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