-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhaproxytime_test.go
291 lines (263 loc) · 9.54 KB
/
haproxytime_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
package main_test
import (
"bytes"
"errors"
"io"
"strings"
"testing"
cmd "github.com/frobware/haproxytime"
)
// mockFailWriter is an io.Writer implementation that simulates a write failure.
// It's used in tests to trigger error handling paths in functions that write to io.Writer.
type mockFailWriter struct{}
func (m mockFailWriter) Write(p []byte) (n int, err error) {
return 0, errors.New("mock write failure")
}
// mockExitHandler is an implementation of the ExitHandler interface used in tests.
// It captures the exit code provided to the Exit method instead of terminating the program.
type mockExitHandler struct {
Exited bool // Exited indicates whether Exit was called
Code int // Code is the exit code passed to Exit
}
func (e *mockExitHandler) Exit(code int) {
e.Exited = true
e.Code = code
}
type errorReader struct{}
func (er *errorReader) Read([]byte) (n int, err error) {
return 0, errors.New("simulated read error")
}
type emptyStringReader struct {
read bool
}
func (er *emptyStringReader) Read([]byte) (n int, err error) {
if er.read {
return 0, io.EOF
}
er.read = true
return 0, nil
}
func TestConvertDuration(t *testing.T) {
tests := []struct {
description string
args []string
stdin io.Reader
expectedExit int
expectedStdout string
expectedStderr string
}{{
description: "Test version flag",
args: []string{"-v"},
expectedExit: 0,
expectedStdout: "",
expectedStderr: "haproxytime <unknown>",
}, {
description: "Test -m flag",
args: []string{"-m"},
expectedExit: 0,
expectedStdout: "2147483647ms",
expectedStderr: "",
}, {
description: "Test -h flag",
args: []string{"-h", "2147483647ms"},
expectedExit: 0,
expectedStdout: "24d20h31m23s647ms",
expectedStderr: "",
}, {
description: "Test -m -h combined",
args: []string{"-m", "-h"},
expectedExit: 0,
expectedStdout: "24d20h31m23s647ms",
expectedStderr: "",
}, {
description: "number of milliseconds in a day from args",
args: []string{"86400000"},
expectedExit: 0,
expectedStdout: "86400000ms",
expectedStderr: "",
}, {
description: "number of milliseconds in a day from stdin",
stdin: strings.NewReader("1d\n"),
expectedExit: 0,
expectedStdout: "86400000ms",
expectedStderr: "",
}, {
description: "number of milliseconds in a day with human-readable output",
args: []string{"-h", "86400000"},
expectedExit: 0,
expectedStdout: "1d",
expectedStderr: "",
}, {
description: "1d as milliseconds",
args: []string{"1d"},
expectedExit: 0,
expectedStdout: "86400000ms",
expectedStderr: "",
}, {
description: "the HAProxy maximum duration",
args: []string{"-h", "24d20h31m23s646ms1000us"},
expectedExit: 0,
expectedStdout: "24d20h31m23s647ms",
expectedStderr: "",
}, {
description: "help flag",
args: []string{"-help"},
expectedExit: 1,
expectedStdout: "",
expectedStderr: cmd.Usage,
}, {
description: "single invalid flag",
args: []string{"-z"},
expectedExit: 1,
expectedStdout: "",
expectedStderr: "flag provided but not defined: -z",
}, {
description: "mix of valid and invalid flags",
args: []string{"-h", "-z", "100ms"},
expectedExit: 1,
expectedStdout: "",
expectedStderr: "flag provided but not defined: -z",
}, {
description: "syntax error reporting from args",
args: []string{"24d20h31m23s647msO000us"},
expectedExit: 1,
expectedStdout: "",
expectedStderr: "syntax error at position 18: invalid number\n24d20h31m23s647msO000us\n ^",
}, {
description: "syntax error reporting from stdin",
stdin: strings.NewReader("24d20h31m23s647msO000us\n"),
expectedExit: 1,
expectedStdout: "",
expectedStderr: "syntax error at position 18: invalid number",
}, {
description: "value exceeds HAProxy's maximum duration from args",
args: []string{"24d20h31m23s647ms1000us"},
expectedExit: 1,
expectedStdout: "",
expectedStderr: "range error at position 18\n24d20h31m23s647ms1000us\n ^",
}, {
description: "value exceeds HAProxy's maximum description from stdin",
stdin: strings.NewReader("24d20h31m23s647ms1000us\n"),
expectedExit: 1,
expectedStdout: "",
expectedStderr: "range error at position 18",
}, {
description: "simulated reading failure",
stdin: &errorReader{},
expectedExit: 1,
expectedStdout: "",
expectedStderr: "error reading: simulated read error",
}, {
description: "overflow error from args",
args: []string{"9223372036855ms"},
expectedExit: 1,
expectedStdout: "",
expectedStderr: "overflow error at position 1\n9223372036855ms\n^",
}, {
description: "overflow error from stdin",
stdin: strings.NewReader("9223372036855ms"),
expectedExit: 1,
expectedStdout: "",
expectedStderr: "overflow error at position 1",
}, {
description: "empty string from stdin",
stdin: &emptyStringReader{},
expectedExit: 0,
expectedStdout: "0ms",
expectedStderr: "",
}, {
description: "empty string from stdin with -h flag",
args: []string{"-h"},
stdin: &emptyStringReader{},
expectedExit: 0,
expectedStdout: "0ms",
expectedStderr: "",
}}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
mockExitHandler := &mockExitHandler{}
exitCode := cmd.ConvertDuration(tc.stdin, stdout, stderr, tc.args, mockExitHandler)
// If mockExitHandler.Exited is true, use
// mockExitHandler.Code as the exit code.
if mockExitHandler.Exited {
exitCode = mockExitHandler.Code
}
if exitCode != tc.expectedExit {
t.Errorf("Expected exit code %d, but got %d", tc.expectedExit, exitCode)
}
actualStdout := strings.TrimSuffix(stdout.String(), "\n")
if actualStdout != tc.expectedStdout {
t.Errorf("Expected stdout:\n<<<%s>>>\nBut got:\n<<<%s>>>", tc.expectedStdout, actualStdout)
}
actualStderr := strings.TrimSuffix(stderr.String(), "\n")
if actualStderr != tc.expectedStderr {
t.Errorf("Expected stderr:\n<<<%s>>>\nBut got:\n<<<%s>>>", tc.expectedStderr, actualStderr)
}
})
}
}
// TestConvertDurationPrintFailure tests the convertDuration function
// to ensure it correctly handles write failures. The test uses
// mockFailWriter to simulate write failures and MockExitHandler to
// capture the exit behavior, verifying that convertDuration exits
// with the expected code when it encounters write errors.
func TestConvertDurationPrintFailure(t *testing.T) {
mockStdin := strings.NewReader("1d")
mockStdout := &mockFailWriter{}
mockStderr := &mockFailWriter{}
mockExitHandler := &mockExitHandler{}
cmd.ConvertDuration(mockStdin, mockStdout, mockStderr, []string{}, mockExitHandler)
// Verify that the mock exit handler was triggered with the
// expected exit code.
if !mockExitHandler.Exited || mockExitHandler.Code != 1 {
t.Errorf("Expected exit with code 1, got exit %v with code %d", mockExitHandler.Exited, mockExitHandler.Code)
}
}
// TestConvertDurationPrintlnFailure tests the convertDuration
// function to ensure it correctly handles write failures in
// safeFprintln. The test uses mockFailWriter to simulate write
// failures and MockExitHandler to capture the exit behavior. It
// verifies that convertDuration exits with the expected code when
// safeFprintln encounters write errors.
func TestConvertDurationPrintlnFailure(t *testing.T) {
mockStdin := strings.NewReader("invalid input")
mockStdout := &bytes.Buffer{}
mockStderr := &mockFailWriter{}
mockExitHandler := &mockExitHandler{}
cmd.ConvertDuration(mockStdin, mockStdout, mockStderr, []string{"-h"}, mockExitHandler)
// Verify that the mock exit handler was triggered with the
// expected exit code.
if !mockExitHandler.Exited || mockExitHandler.Code != 1 {
t.Errorf("Expected exit with code 1, got exit %v with code %d", mockExitHandler.Exited, mockExitHandler.Code)
}
}
// TestPrintPositionalErrorWithNonPositionalError directly tests the
// behavior of PrintPositionalError. Ordinarily, we would prefer to
// exercise this test through the convertDuration function to mimic
// real-world usage more closely. However, integrating the required
// dependency injection to induce a non-positional error through
// convertDuration proves to be complex. Implementing such dependency
// injection would require significant modifications to the
// convertDuration function and its associated code paths, potentially
// leading to an unnatural design in the overall program. Therefore,
// this direct approach is used for testing to balance thoroughness in
// testing with maintaining the simplicity and readability of the
// application's code.
func TestPrintPositionalErrorWithNonPositionalError(t *testing.T) {
var nonPositionalError = errors.New("non-positional error")
mockWriter := &bytes.Buffer{}
mockExitHandler := &mockExitHandler{}
cmd.PrintPositionalError(mockWriter, mockExitHandler, nonPositionalError, "inputArg")
// Check if the generic error message was printed.
expectedOutput := "Unexpected error: non-positional error\n"
if gotOutput := mockWriter.String(); gotOutput != expectedOutput {
t.Errorf("Expected output %q, got %q", expectedOutput, gotOutput)
}
// Verify if the mock exit handler was triggered with the
// expected exit code.
if !mockExitHandler.Exited || mockExitHandler.Code != 1 {
t.Errorf("Expected exit with code 1, got exit %v with code %d", mockExitHandler.Exited, mockExitHandler.Code)
}
}