Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit eb4f989

Browse files
authored
surface panic when calling Finish is implicit (#478)
When Finsh is called from Cleanup, it is not done so in a deffered context. Because of this, we can't recover from panics. The testing package will re-surface any panics but only after the Cleanup functions is executed. Because we were calling t.Fatal we were hiding panics from users of the library when they were using Finish implicitly. Fatal calls runtime.Goexit() under the hood so the panic never gets the chance to be handled by the testing package. Also, removed logging as after thinking about it more it seems a little aggressive to push the feature onto users. Note, when there is a panic and Finish is called from the Cleanup function that a few extra lines will be logged from the calls to ErrorF. This seems like a small price to pay to get the context of the panic.
1 parent e40fff5 commit eb4f989

File tree

7 files changed

+171
-12
lines changed

7 files changed

+171
-12
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ script:
1717
- ./ci/check_go_lint.sh
1818
- ./ci/check_go_generate.sh
1919
- ./ci/check_go_mod.sh
20+
- ./ci/check_panic_handling.sh
2021
- go test -v ./...

ci/check_panic_handling.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
# Copyright 2010 Google LLC.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# This script is used to ensure that panics are properly reported in tests.
17+
18+
set -eux
19+
20+
pushd mockgen/internal/tests/panicing_test
21+
go test -v -tags=panictest -run TestDanger_Panics_Explicit | grep "Danger, Will Robinson!"
22+
go test -v -tags=panictest -run TestDanger_Panics_Implicit | grep "Danger, Will Robinson!"
23+
popd

gomock/controller.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func NewController(t TestReporter) *Controller {
136136
if c, ok := isCleanuper(ctrl.T); ok {
137137
c.Cleanup(func() {
138138
ctrl.T.Helper()
139-
ctrl.Finish()
139+
ctrl.finish(true, nil)
140140
})
141141
}
142142

@@ -260,6 +260,13 @@ func (ctrl *Controller) Call(receiver interface{}, method string, args ...interf
260260
// were called. It should be invoked for each Controller. It is not idempotent
261261
// and therefore can only be invoked once.
262262
func (ctrl *Controller) Finish() {
263+
// If we're currently panicking, probably because this is a deferred call.
264+
// This must be recovered in the deferred function.
265+
err := recover()
266+
ctrl.finish(false, err)
267+
}
268+
269+
func (ctrl *Controller) finish(cleanup bool, panicErr interface{}) {
263270
ctrl.T.Helper()
264271

265272
ctrl.mu.Lock()
@@ -269,19 +276,13 @@ func (ctrl *Controller) Finish() {
269276
if _, ok := isCleanuper(ctrl.T); !ok {
270277
ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.")
271278
}
272-
// provide a log message to guide users to remove `defer ctrl.Finish()` in Go 1.14+
273-
tr := unwrapTestReporter(ctrl.T)
274-
if l, ok := tr.(interface{ Log(args ...interface{}) }); ok {
275-
l.Log("In Go 1.14+ you no longer need to `ctrl.Finish()` if a *testing.T is passed to `NewController(...)`")
276-
}
277279
return
278280
}
279281
ctrl.finished = true
280282

281-
// If we're currently panicking, probably because this is a deferred call,
282-
// pass through the panic.
283-
if err := recover(); err != nil {
284-
panic(err)
283+
// Short-circuit, pass through the panic.
284+
if panicErr != nil {
285+
panic(panicErr)
285286
}
286287

287288
// Check that all remaining expected calls are satisfied.
@@ -290,7 +291,11 @@ func (ctrl *Controller) Finish() {
290291
ctrl.T.Errorf("missing call(s) to %v", call)
291292
}
292293
if len(failures) != 0 {
293-
ctrl.T.Fatalf("aborting test due to missing call(s)")
294+
if !cleanup {
295+
ctrl.T.Fatalf("aborting test due to missing call(s)")
296+
return
297+
}
298+
ctrl.T.Errorf("aborting test due to missing call(s)")
294299
}
295300
}
296301

gomock/controller_114_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (e *ErrorReporter) Cleanup(f func()) {
3030
func TestMultipleDefers(t *testing.T) {
3131
reporter := NewErrorReporter(t)
3232
reporter.Cleanup(func() {
33-
reporter.assertLogf("In Go 1.14+ you no longer need to `ctrl.Finish()` if a *testing.T is passed to `NewController(...)`")
33+
reporter.assertPass("No errors for multiple calls to Finish")
3434
})
3535
ctrl := gomock.NewController(reporter)
3636
ctrl.Finish()

mockgen/internal/tests/panicing_test/mock_test.go

Lines changed: 62 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package paniccode
16+
17+
//go:generate mockgen --source=panic.go --destination=mock_test.go --package=paniccode
18+
19+
type Foo interface {
20+
Bar() string
21+
Baz() string
22+
}
23+
24+
func Danger(f Foo) {
25+
if f.Bar() == "Bar" {
26+
panic("Danger, Will Robinson!")
27+
}
28+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// +build panictest
2+
3+
// Copyright 2020 Google LLC
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package paniccode
18+
19+
import (
20+
"testing"
21+
22+
"github.com/golang/mock/gomock"
23+
)
24+
25+
func TestDanger_Panics_Explicit(t *testing.T) {
26+
ctrl := gomock.NewController(t)
27+
defer ctrl.Finish()
28+
mock := NewMockFoo(ctrl)
29+
mock.EXPECT().Bar().Return("Bar")
30+
mock.EXPECT().Bar().Return("Baz")
31+
Danger(mock)
32+
}
33+
34+
func TestDanger_Panics_Implicit(t *testing.T) {
35+
ctrl := gomock.NewController(t)
36+
mock := NewMockFoo(ctrl)
37+
mock.EXPECT().Bar().Return("Bar")
38+
mock.EXPECT().Bar().Return("Baz")
39+
Danger(mock)
40+
}

0 commit comments

Comments
 (0)