github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/command/helpers_test.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "reflect" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/hashicorp/nomad/api" 16 "github.com/hashicorp/nomad/helper" 17 "github.com/hashicorp/nomad/helper/flatmap" 18 "github.com/kr/pretty" 19 "github.com/mitchellh/cli" 20 "github.com/stretchr/testify/require" 21 ) 22 23 func TestHelpers_FormatKV(t *testing.T) { 24 t.Parallel() 25 in := []string{"alpha|beta", "charlie|delta", "echo|"} 26 out := formatKV(in) 27 28 expect := "alpha = beta\n" 29 expect += "charlie = delta\n" 30 expect += "echo = <none>" 31 32 if out != expect { 33 t.Fatalf("expect: %s, got: %s", expect, out) 34 } 35 } 36 37 func TestHelpers_FormatList(t *testing.T) { 38 t.Parallel() 39 in := []string{"alpha|beta||delta"} 40 out := formatList(in) 41 42 expect := "alpha beta <none> delta" 43 44 if out != expect { 45 t.Fatalf("expect: %s, got: %s", expect, out) 46 } 47 } 48 49 func TestHelpers_NodeID(t *testing.T) { 50 t.Parallel() 51 srv, _, _ := testServer(t, false, nil) 52 defer srv.Shutdown() 53 54 meta := Meta{Ui: new(cli.MockUi)} 55 client, err := meta.Client() 56 if err != nil { 57 t.FailNow() 58 } 59 60 // This is because there is no client 61 if _, err := getLocalNodeID(client); err == nil { 62 t.Fatalf("getLocalNodeID() should fail") 63 } 64 } 65 66 func TestHelpers_LineLimitReader_NoTimeLimit(t *testing.T) { 67 t.Parallel() 68 helloString := `hello 69 world 70 this 71 is 72 a 73 test` 74 75 noLines := "jskdfhjasdhfjkajkldsfdlsjkahfkjdsafa" 76 77 cases := []struct { 78 Input string 79 Output string 80 Lines int 81 SearchLimit int 82 }{ 83 { 84 Input: helloString, 85 Output: helloString, 86 Lines: 6, 87 SearchLimit: 1000, 88 }, 89 { 90 Input: helloString, 91 Output: `world 92 this 93 is 94 a 95 test`, 96 Lines: 5, 97 SearchLimit: 1000, 98 }, 99 { 100 Input: helloString, 101 Output: `test`, 102 Lines: 1, 103 SearchLimit: 1000, 104 }, 105 { 106 Input: helloString, 107 Output: "", 108 Lines: 0, 109 SearchLimit: 1000, 110 }, 111 { 112 Input: helloString, 113 Output: helloString, 114 Lines: 6, 115 SearchLimit: 1, // Exceed the limit 116 }, 117 { 118 Input: noLines, 119 Output: noLines, 120 Lines: 10, 121 SearchLimit: 1000, 122 }, 123 { 124 Input: noLines, 125 Output: noLines, 126 Lines: 10, 127 SearchLimit: 2, 128 }, 129 } 130 131 for i, c := range cases { 132 in := ioutil.NopCloser(strings.NewReader(c.Input)) 133 limit := NewLineLimitReader(in, c.Lines, c.SearchLimit, 0) 134 outBytes, err := ioutil.ReadAll(limit) 135 if err != nil { 136 t.Fatalf("case %d failed: %v", i, err) 137 } 138 139 out := string(outBytes) 140 if out != c.Output { 141 t.Fatalf("case %d: got %q; want %q", i, out, c.Output) 142 } 143 } 144 } 145 146 type testReadCloser struct { 147 data chan []byte 148 } 149 150 func (t *testReadCloser) Read(p []byte) (n int, err error) { 151 select { 152 case b, ok := <-t.data: 153 if !ok { 154 return 0, io.EOF 155 } 156 157 return copy(p, b), nil 158 case <-time.After(10 * time.Millisecond): 159 return 0, nil 160 } 161 } 162 163 func (t *testReadCloser) Close() error { 164 close(t.data) 165 return nil 166 } 167 168 func TestHelpers_LineLimitReader_TimeLimit(t *testing.T) { 169 t.Parallel() 170 // Create the test reader 171 in := &testReadCloser{data: make(chan []byte)} 172 173 // Set up the reader such that it won't hit the line/buffer limit and could 174 // only terminate if it hits the time limit 175 limit := NewLineLimitReader(in, 1000, 1000, 100*time.Millisecond) 176 177 expected := []byte("hello world") 178 179 errCh := make(chan error) 180 resultCh := make(chan []byte) 181 go func() { 182 defer close(resultCh) 183 defer close(errCh) 184 outBytes, err := ioutil.ReadAll(limit) 185 if err != nil { 186 errCh <- fmt.Errorf("ReadAll failed: %v", err) 187 return 188 } 189 resultCh <- outBytes 190 }() 191 192 // Send the data 193 in.data <- expected 194 in.Close() 195 196 select { 197 case err := <-errCh: 198 if err != nil { 199 t.Fatalf("ReadAll: %v", err) 200 } 201 case outBytes := <-resultCh: 202 if !reflect.DeepEqual(outBytes, expected) { 203 t.Fatalf("got:%s, expected,%s", string(outBytes), string(expected)) 204 } 205 case <-time.After(1 * time.Second): 206 t.Fatalf("did not exit by time limit") 207 } 208 } 209 210 const ( 211 job = `job "job1" { 212 type = "service" 213 datacenters = [ "dc1" ] 214 group "group1" { 215 count = 1 216 task "task1" { 217 driver = "exec" 218 resources = {} 219 } 220 restart{ 221 attempts = 10 222 mode = "delay" 223 interval = "15s" 224 } 225 } 226 }` 227 ) 228 229 var ( 230 expectedApiJob = &api.Job{ 231 ID: helper.StringToPtr("job1"), 232 Name: helper.StringToPtr("job1"), 233 Type: helper.StringToPtr("service"), 234 Datacenters: []string{"dc1"}, 235 TaskGroups: []*api.TaskGroup{ 236 { 237 Name: helper.StringToPtr("group1"), 238 Count: helper.IntToPtr(1), 239 RestartPolicy: &api.RestartPolicy{ 240 Attempts: helper.IntToPtr(10), 241 Interval: helper.TimeToPtr(15 * time.Second), 242 Mode: helper.StringToPtr("delay"), 243 }, 244 245 Tasks: []*api.Task{ 246 { 247 Driver: "exec", 248 Name: "task1", 249 Resources: &api.Resources{}, 250 }, 251 }, 252 }, 253 }, 254 } 255 ) 256 257 // Test APIJob with local jobfile 258 func TestJobGetter_LocalFile(t *testing.T) { 259 t.Parallel() 260 fh, err := ioutil.TempFile("", "nomad") 261 if err != nil { 262 t.Fatalf("err: %s", err) 263 } 264 defer os.Remove(fh.Name()) 265 _, err = fh.WriteString(job) 266 if err != nil { 267 t.Fatalf("err: %s", err) 268 } 269 270 j := &JobGetter{} 271 aj, err := j.ApiJob(fh.Name()) 272 if err != nil { 273 t.Fatalf("err: %s", err) 274 } 275 276 if !reflect.DeepEqual(expectedApiJob, aj) { 277 eflat := flatmap.Flatten(expectedApiJob, nil, false) 278 aflat := flatmap.Flatten(aj, nil, false) 279 t.Fatalf("got:\n%v\nwant:\n%v", aflat, eflat) 280 } 281 } 282 283 // Test StructJob with jobfile from HTTP Server 284 func TestJobGetter_HTTPServer(t *testing.T) { 285 t.Parallel() 286 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 287 fmt.Fprintf(w, job) 288 }) 289 go http.ListenAndServe("127.0.0.1:12345", nil) 290 291 // Wait until HTTP Server starts certainly 292 time.Sleep(100 * time.Millisecond) 293 294 j := &JobGetter{} 295 aj, err := j.ApiJob("http://127.0.0.1:12345/") 296 if err != nil { 297 t.Fatalf("err: %s", err) 298 } 299 if !reflect.DeepEqual(expectedApiJob, aj) { 300 for _, d := range pretty.Diff(expectedApiJob, aj) { 301 t.Logf(d) 302 } 303 t.Fatalf("Unexpected file") 304 } 305 } 306 307 func TestPrettyTimeDiff(t *testing.T) { 308 // Grab the time and truncate to the nearest second. This allows our tests 309 // to be deterministic since we don't have to worry about rounding. 310 now := time.Now().Truncate(time.Second) 311 312 test_cases := []struct { 313 t1 time.Time 314 t2 time.Time 315 exp string 316 }{ 317 {now, time.Unix(0, 0), ""}, // This is the upgrade path case 318 {now, now.Add(-10 * time.Millisecond), "0s ago"}, 319 {now, now.Add(-740 * time.Second), "12m20s ago"}, 320 {now, now.Add(-12 * time.Minute), "12m ago"}, 321 {now, now.Add(-60 * time.Minute), "1h ago"}, 322 {now, now.Add(-80 * time.Minute), "1h20m ago"}, 323 {now, now.Add(-6 * time.Hour), "6h ago"}, 324 {now.Add(-6 * time.Hour), now, "6h from now"}, 325 {now, now.Add(-22165 * time.Second), "6h9m ago"}, 326 {now, now.Add(-100 * time.Hour), "4d4h ago"}, 327 {now, now.Add(-438000 * time.Minute), "10mo4d ago"}, 328 {now, now.Add(-20460 * time.Hour), "2y4mo ago"}, 329 } 330 for _, tc := range test_cases { 331 t.Run(tc.exp, func(t *testing.T) { 332 out := prettyTimeDiff(tc.t2, tc.t1) 333 if out != tc.exp { 334 t.Fatalf("expected :%v but got :%v", tc.exp, out) 335 } 336 }) 337 } 338 339 var t1 time.Time 340 out := prettyTimeDiff(t1, time.Now()) 341 342 if out != "" { 343 t.Fatalf("Expected empty output but got:%v", out) 344 } 345 346 } 347 348 // TestUiErrorWriter asserts that writer buffers and 349 func TestUiErrorWriter(t *testing.T) { 350 t.Parallel() 351 352 var outBuf, errBuf bytes.Buffer 353 ui := &cli.BasicUi{ 354 Writer: &outBuf, 355 ErrorWriter: &errBuf, 356 } 357 358 w := &uiErrorWriter{ui: ui} 359 360 inputs := []string{ 361 "some line\n", 362 "multiple\nlines\r\nhere", 363 " with followup\nand", 364 " more lines ", 365 " without new line ", 366 "until here\nand then", 367 "some more", 368 } 369 370 partialAcc := "" 371 for _, in := range inputs { 372 n, err := w.Write([]byte(in)) 373 require.NoError(t, err) 374 require.Equal(t, len(in), n) 375 376 // assert that writer emits partial result until last new line 377 partialAcc += strings.ReplaceAll(in, "\r\n", "\n") 378 lastNL := strings.LastIndex(partialAcc, "\n") 379 require.Equal(t, partialAcc[:lastNL+1], errBuf.String()) 380 } 381 382 require.Empty(t, outBuf.String()) 383 384 // note that the \r\n got replaced by \n 385 expectedErr := "some line\nmultiple\nlines\nhere with followup\nand more lines without new line until here\n" 386 require.Equal(t, expectedErr, errBuf.String()) 387 388 // close emits the final line 389 err := w.Close() 390 require.NoError(t, err) 391 392 expectedErr += "and thensome more\n" 393 require.Equal(t, expectedErr, errBuf.String()) 394 }