|
21 | 21 | package tchannel |
22 | 22 |
|
23 | 23 | import ( |
| 24 | + "context" |
| 25 | + "errors" |
| 26 | + "fmt" |
24 | 27 | "io" |
25 | 28 | "regexp" |
26 | 29 | "testing" |
@@ -73,3 +76,63 @@ func TestRelayMetricsKey(t *testing.T) { |
73 | 76 | assert.Equal(t, "relay-"+code.MetricsKey(), code.relayMetricsKey(), "Unexpected relay metrics key for %v", code) |
74 | 77 | } |
75 | 78 | } |
| 79 | + |
| 80 | +func TestSystemErrorIs(t *testing.T) { |
| 81 | + // These targets come from the standard library's context package on purpose: |
| 82 | + // callers use errors.Is(err, context.DeadlineExceeded) with the stdlib |
| 83 | + // sentinels, and this test proves a SystemError matches them. |
| 84 | + tests := []struct { |
| 85 | + name string |
| 86 | + err error |
| 87 | + target error |
| 88 | + want bool |
| 89 | + }{ |
| 90 | + {"timeout sentinel matches DeadlineExceeded", ErrTimeout, context.DeadlineExceeded, true}, |
| 91 | + {"cancelled sentinel matches Canceled", ErrRequestCancelled, context.Canceled, true}, |
| 92 | + {"timeout does not match Canceled", ErrTimeout, context.Canceled, false}, |
| 93 | + {"cancelled does not match DeadlineExceeded", ErrRequestCancelled, context.DeadlineExceeded, false}, |
| 94 | + |
| 95 | + // Matching is keyed on the wire error code, not the message, so timeouts |
| 96 | + // and cancellations rebuilt from the wire (including from non-Go peers |
| 97 | + // that send a different message) are still recognized. |
| 98 | + {"wire timeout with custom message matches DeadlineExceeded", NewSystemError(ErrCodeTimeout, "connection timed out"), context.DeadlineExceeded, true}, |
| 99 | + {"wire cancel with custom message matches Canceled", NewSystemError(ErrCodeCancelled, "peer cancelled"), context.Canceled, true}, |
| 100 | + |
| 101 | + // Other codes never match the context sentinels. |
| 102 | + {"busy does not match DeadlineExceeded", ErrServerBusy, context.DeadlineExceeded, false}, |
| 103 | + {"busy does not match Canceled", ErrServerBusy, context.Canceled, false}, |
| 104 | + {"bad request does not match DeadlineExceeded", ErrTimeoutRequired, context.DeadlineExceeded, false}, |
| 105 | + {"timeout does not match an unrelated error", ErrTimeout, io.EOF, false}, |
| 106 | + } |
| 107 | + |
| 108 | + for _, tt := range tests { |
| 109 | + t.Run(tt.name, func(t *testing.T) { |
| 110 | + assert.Equal(t, tt.want, errors.Is(tt.err, tt.target)) |
| 111 | + }) |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +func TestSystemErrorIsThroughWrap(t *testing.T) { |
| 116 | + // errors.Is must find the context sentinel when a SystemError is wrapped |
| 117 | + // further up the chain with %w. |
| 118 | + err := fmt.Errorf("call to service failed: %w", ErrTimeout) |
| 119 | + assert.True(t, errors.Is(err, context.DeadlineExceeded), |
| 120 | + "errors.Is should see context.DeadlineExceeded through a wrapped timeout") |
| 121 | + |
| 122 | + err = fmt.Errorf("call to service failed: %w", NewSystemError(ErrCodeCancelled, "peer cancelled")) |
| 123 | + assert.True(t, errors.Is(err, context.Canceled), |
| 124 | + "errors.Is should see context.Canceled through a wrapped cancellation") |
| 125 | +} |
| 126 | + |
| 127 | +func TestSystemErrorIdentityUnchanged(t *testing.T) { |
| 128 | + // Is() is purely additive: it changes no SystemError values, so existing |
| 129 | + // equality-based comparisons and code extraction keep working. A timeout |
| 130 | + // rebuilt from the wire still equals the ErrTimeout sentinel by value, and |
| 131 | + // the sentinels still report their codes. |
| 132 | + assert.Equal(t, ErrTimeout, NewSystemError(ErrCodeTimeout, "timeout"), |
| 133 | + "a rebuilt wire timeout must still equal the ErrTimeout sentinel by value") |
| 134 | + assert.Equal(t, ErrRequestCancelled, NewSystemError(ErrCodeCancelled, "request cancelled"), |
| 135 | + "a rebuilt wire cancellation must still equal the ErrRequestCancelled sentinel by value") |
| 136 | + assert.Equal(t, ErrCodeTimeout, GetSystemErrorCode(ErrTimeout)) |
| 137 | + assert.Equal(t, ErrCodeCancelled, GetSystemErrorCode(ErrRequestCancelled)) |
| 138 | +} |
0 commit comments