Skip to content

Commit 65ee9c6

Browse files
committed
Deterministic calc of POSIX return code for Fatal
1 parent d38507c commit 65ee9c6

File tree

5 files changed

+45
-14
lines changed

5 files changed

+45
-14
lines changed

internal/exit/exit.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,19 @@ package exit
2424

2525
import "os"
2626

27-
var real = func() { os.Exit(1) }
27+
var real = func(code int) { os.Exit(code) }
2828

2929
// Exit normally terminates the process by calling os.Exit(1). If the package
3030
// is stubbed, it instead records a call in the testing spy.
31-
func Exit() {
32-
real()
31+
func Exit(code int) {
32+
real(code)
3333
}
3434

3535
// A StubbedExit is a testing fake for os.Exit.
3636
type StubbedExit struct {
3737
Exited bool
38-
prev func()
38+
Code int
39+
prev func(code int)
3940
}
4041

4142
// Stub substitutes a fake for the call to os.Exit(1).
@@ -59,6 +60,7 @@ func (se *StubbedExit) Unstub() {
5960
real = se.prev
6061
}
6162

62-
func (se *StubbedExit) exit() {
63+
func (se *StubbedExit) exit(code int) {
6364
se.Exited = true
65+
se.Code = code
6466
}

internal/exit/exit_test.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,33 @@
1818
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1919
// THE SOFTWARE.
2020

21-
package exit
21+
package exit_test
2222

2323
import (
2424
"testing"
2525

2626
"github.com/stretchr/testify/assert"
27+
"go.uber.org/zap/internal/exit"
2728
)
2829

2930
func TestStub(t *testing.T) {
31+
type want struct {
32+
exit bool
33+
code int
34+
}
3035
tests := []struct {
31-
f func()
32-
want bool
36+
f func()
37+
want
3338
}{
34-
{Exit, true},
35-
{func() {}, false},
39+
{func() {
40+
exit.Exit(42)
41+
}, want{exit: true, code: 42}},
42+
{func() {}, want{}},
3643
}
3744

3845
for _, tt := range tests {
39-
s := WithStub(tt.f)
40-
assert.Equal(t, tt.want, s.Exited, "Stub captured unexpected exit value.")
46+
s := exit.WithStub(tt.f)
47+
assert.Equal(t, tt.want.exit, s.Exited, "Stub captured unexpected exit value.")
48+
assert.Equal(t, tt.want.code, s.Code, "Stub captured unexpected exit value.")
4149
}
4250
}

logger_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,22 @@ func TestLoggerAlwaysFatals(t *testing.T) {
213213
// Users can disable writing out fatal-level logs, but calls to logger.Fatal()
214214
// should still terminate the process.
215215
withLogger(t, FatalLevel+1, nil, func(logger *Logger, logs *observer.ObservedLogs) {
216-
stub := exit.WithStub(func() { logger.Fatal("") })
216+
stub := exit.WithStub(func() { logger.Fatal("foo") })
217217
assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.")
218+
assert.Equal(t, 38, stub.Code, "Expected calls to logger.Fatal to terminate process with predictable retcode.")
219+
220+
err := errors.New("bar")
221+
stub = exit.WithStub(func() { logger.Fatal("foo", Error(err)) })
222+
assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.")
223+
assert.Equal(t, 129, stub.Code, "Expected calls to logger.Fatal to terminate process with predictable retcode.")
218224

219225
stub = exit.WithStub(func() {
220226
if ce := logger.Check(FatalLevel, ""); ce != nil {
221227
ce.Write()
222228
}
223229
})
224230
assert.True(t, stub.Exited, "Expected calls to logger.Check(FatalLevel, ...) to terminate process.")
231+
assert.Equal(t, 1, stub.Code, "Expected calls to logger.Check(FatalLevel, ...) to terminate process with predictable retcode.")
225232

226233
assert.Equal(t, 0, logs.Len(), "Shouldn't write out logs when fatal-level logging is disabled.")
227234
})

zapcore/entry.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package zapcore
2222

2323
import (
2424
"fmt"
25+
"hash/crc32"
2526
"runtime"
2627
"strings"
2728
"sync"
@@ -231,7 +232,7 @@ func (ce *CheckedEntry) Write(fields ...Field) {
231232
case WriteThenPanic:
232233
panic(msg)
233234
case WriteThenFatal:
234-
exit.Exit()
235+
exit.Exit(retcode(ce, fields))
235236
case WriteThenGoexit:
236237
runtime.Goexit()
237238
}
@@ -260,3 +261,14 @@ func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry
260261
ce.should = should
261262
return ce
262263
}
264+
265+
func retcode(ce *CheckedEntry, fields []Field) int {
266+
msg := ce.Message
267+
for _, field := range fields {
268+
if field.Type == ErrorType {
269+
msg = field.Interface.(error).Error()
270+
break
271+
}
272+
}
273+
return int(crc32.ChecksumIEEE([]byte(msg)))%254 + 1
274+
}

zapcore/entry_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,11 @@ func TestCheckedEntryWrite(t *testing.T) {
124124
t.Run("WriteThenFatal", func(t *testing.T) {
125125
var ce *CheckedEntry
126126
ce = ce.Should(Entry{}, WriteThenFatal)
127+
ce.Message = t.Name()
127128
stub := exit.WithStub(func() {
128129
ce.Write()
129130
})
130131
assert.True(t, stub.Exited, "Expected to exit when WriteThenFatal is set.")
132+
assert.Equal(t, 57, stub.Code, "Expected to exit with specific code when WriteThenFatal is set.")
131133
})
132134
}

0 commit comments

Comments
 (0)