github.com/moby/docker@v26.1.3+incompatible/libnetwork/etchosts/etchosts_test.go (about) 1 package etchosts 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "testing" 9 10 "golang.org/x/sync/errgroup" 11 "gotest.tools/v3/assert" 12 is "gotest.tools/v3/assert/cmp" 13 ) 14 15 func TestBuildDefault(t *testing.T) { 16 file, err := os.CreateTemp("", "") 17 if err != nil { 18 t.Fatal(err) 19 } 20 defer os.Remove(file.Name()) 21 22 // check that /etc/hosts has consistent ordering 23 for i := 0; i <= 5; i++ { 24 err = Build(file.Name(), nil) 25 if err != nil { 26 t.Fatal(err) 27 } 28 29 content, err := os.ReadFile(file.Name()) 30 if err != nil { 31 t.Fatal(err) 32 } 33 expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n" 34 35 if expected != string(content) { 36 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 37 } 38 } 39 } 40 41 func TestBuildNoIPv6(t *testing.T) { 42 d := t.TempDir() 43 filename := filepath.Join(d, "hosts") 44 45 err := BuildNoIPv6(filename, []Record{ 46 { 47 Hosts: "another.example", 48 IP: "fdbb:c59c:d015::3", 49 }, 50 { 51 Hosts: "another.example", 52 IP: "10.11.12.13", 53 }, 54 }) 55 assert.NilError(t, err) 56 content, err := os.ReadFile(filename) 57 assert.NilError(t, err) 58 assert.Check(t, is.DeepEqual(string(content), "127.0.0.1\tlocalhost\n10.11.12.13\tanother.example\n")) 59 } 60 61 func TestUpdate(t *testing.T) { 62 file, err := os.CreateTemp("", "") 63 if err != nil { 64 t.Fatal(err) 65 } 66 defer os.Remove(file.Name()) 67 68 if err := Build(file.Name(), []Record{ 69 { 70 "testhostname.testdomainname testhostname", 71 "10.11.12.13", 72 }, 73 }); err != nil { 74 t.Fatal(err) 75 } 76 77 content, err := os.ReadFile(file.Name()) 78 if err != nil { 79 t.Fatal(err) 80 } 81 82 if expected := "10.11.12.13\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) { 83 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 84 } 85 86 if err := Update(file.Name(), "1.1.1.1", "testhostname"); err != nil { 87 t.Fatal(err) 88 } 89 90 content, err = os.ReadFile(file.Name()) 91 if err != nil { 92 t.Fatal(err) 93 } 94 95 if expected := "1.1.1.1\ttesthostname.testdomainname testhostname\n"; !bytes.Contains(content, []byte(expected)) { 96 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 97 } 98 } 99 100 // This regression test ensures that when a host is given a new IP 101 // via the Update function that other hosts which start with the 102 // same name as the targeted host are not erroneously updated as well. 103 // In the test example, if updating a host called "prefix", unrelated 104 // hosts named "prefixAndMore" or "prefix2" or anything else starting 105 // with "prefix" should not be changed. For more information see 106 // GitHub issue #603. 107 func TestUpdateIgnoresPrefixedHostname(t *testing.T) { 108 file, err := os.CreateTemp("", "") 109 if err != nil { 110 t.Fatal(err) 111 } 112 defer os.Remove(file.Name()) 113 114 if err := Build(file.Name(), []Record{ 115 { 116 Hosts: "prefix", 117 IP: "2.2.2.2", 118 }, 119 { 120 Hosts: "prefixAndMore", 121 IP: "3.3.3.3", 122 }, 123 { 124 Hosts: "unaffectedHost", 125 IP: "4.4.4.4", 126 }, 127 }); err != nil { 128 t.Fatal(err) 129 } 130 131 content, err := os.ReadFile(file.Name()) 132 if err != nil { 133 t.Fatal(err) 134 } 135 136 if expected := "2.2.2.2\tprefix\n3.3.3.3\tprefixAndMore\n4.4.4.4\tunaffectedHost\n"; !bytes.Contains(content, []byte(expected)) { 137 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 138 } 139 140 if err := Update(file.Name(), "5.5.5.5", "prefix"); err != nil { 141 t.Fatal(err) 142 } 143 144 content, err = os.ReadFile(file.Name()) 145 if err != nil { 146 t.Fatal(err) 147 } 148 149 if expected := "5.5.5.5\tprefix\n3.3.3.3\tprefixAndMore\n4.4.4.4\tunaffectedHost\n"; !bytes.Contains(content, []byte(expected)) { 150 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 151 } 152 } 153 154 // This regression test covers the host prefix issue for the 155 // Delete function. In the test example, if deleting a host called 156 // "prefix", an unrelated host called "prefixAndMore" should not 157 // be deleted. For more information see GitHub issue #603. 158 func TestDeleteIgnoresPrefixedHostname(t *testing.T) { 159 file, err := os.CreateTemp("", "") 160 if err != nil { 161 t.Fatal(err) 162 } 163 defer os.Remove(file.Name()) 164 165 err = Build(file.Name(), nil) 166 if err != nil { 167 t.Fatal(err) 168 } 169 170 if err := Add(file.Name(), []Record{ 171 { 172 Hosts: "prefix", 173 IP: "1.1.1.1", 174 }, 175 { 176 Hosts: "prefixAndMore", 177 IP: "2.2.2.2", 178 }, 179 }); err != nil { 180 t.Fatal(err) 181 } 182 183 if err := Delete(file.Name(), []Record{ 184 { 185 Hosts: "prefix", 186 IP: "1.1.1.1", 187 }, 188 }); err != nil { 189 t.Fatal(err) 190 } 191 192 content, err := os.ReadFile(file.Name()) 193 if err != nil { 194 t.Fatal(err) 195 } 196 197 if expected := "2.2.2.2\tprefixAndMore\n"; !bytes.Contains(content, []byte(expected)) { 198 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 199 } 200 201 if expected := "1.1.1.1\tprefix\n"; bytes.Contains(content, []byte(expected)) { 202 t.Fatalf("Did not expect to find '%s' got '%s'", expected, content) 203 } 204 } 205 206 func TestAddEmpty(t *testing.T) { 207 file, err := os.CreateTemp("", "") 208 if err != nil { 209 t.Fatal(err) 210 } 211 defer os.Remove(file.Name()) 212 213 err = Build(file.Name(), nil) 214 if err != nil { 215 t.Fatal(err) 216 } 217 218 if err := Add(file.Name(), []Record{}); err != nil { 219 t.Fatal(err) 220 } 221 } 222 223 func TestAdd(t *testing.T) { 224 file, err := os.CreateTemp("", "") 225 if err != nil { 226 t.Fatal(err) 227 } 228 defer os.Remove(file.Name()) 229 230 err = Build(file.Name(), nil) 231 if err != nil { 232 t.Fatal(err) 233 } 234 235 if err := Add(file.Name(), []Record{ 236 { 237 Hosts: "testhostname", 238 IP: "2.2.2.2", 239 }, 240 }); err != nil { 241 t.Fatal(err) 242 } 243 244 content, err := os.ReadFile(file.Name()) 245 if err != nil { 246 t.Fatal(err) 247 } 248 249 if expected := "2.2.2.2\ttesthostname\n"; !bytes.Contains(content, []byte(expected)) { 250 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 251 } 252 } 253 254 func TestDeleteEmpty(t *testing.T) { 255 file, err := os.CreateTemp("", "") 256 if err != nil { 257 t.Fatal(err) 258 } 259 defer os.Remove(file.Name()) 260 261 err = Build(file.Name(), nil) 262 if err != nil { 263 t.Fatal(err) 264 } 265 266 if err := Delete(file.Name(), []Record{}); err != nil { 267 t.Fatal(err) 268 } 269 } 270 271 func TestDeleteNewline(t *testing.T) { 272 file, err := os.CreateTemp("", "") 273 if err != nil { 274 t.Fatal(err) 275 } 276 defer os.Remove(file.Name()) 277 278 b := []byte("\n") 279 if _, err := file.Write(b); err != nil { 280 t.Fatal(err) 281 } 282 283 rec := []Record{ 284 { 285 Hosts: "prefix", 286 IP: "2.2.2.2", 287 }, 288 } 289 if err := Delete(file.Name(), rec); err != nil { 290 t.Fatal(err) 291 } 292 } 293 294 func TestDelete(t *testing.T) { 295 file, err := os.CreateTemp("", "") 296 if err != nil { 297 t.Fatal(err) 298 } 299 defer os.Remove(file.Name()) 300 301 err = Build(file.Name(), nil) 302 if err != nil { 303 t.Fatal(err) 304 } 305 306 if err := Add(file.Name(), []Record{ 307 { 308 Hosts: "testhostname1", 309 IP: "1.1.1.1", 310 }, 311 { 312 Hosts: "testhostname2", 313 IP: "2.2.2.2", 314 }, 315 { 316 Hosts: "testhostname3", 317 IP: "3.3.3.3", 318 }, 319 }); err != nil { 320 t.Fatal(err) 321 } 322 323 if err := Delete(file.Name(), []Record{ 324 { 325 Hosts: "testhostname1", 326 IP: "1.1.1.1", 327 }, 328 { 329 Hosts: "testhostname3", 330 IP: "3.3.3.3", 331 }, 332 }); err != nil { 333 t.Fatal(err) 334 } 335 336 content, err := os.ReadFile(file.Name()) 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 if expected := "2.2.2.2\ttesthostname2\n"; !bytes.Contains(content, []byte(expected)) { 342 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 343 } 344 345 if expected := "1.1.1.1\ttesthostname1\n"; bytes.Contains(content, []byte(expected)) { 346 t.Fatalf("Did not expect to find '%s' got '%s'", expected, content) 347 } 348 } 349 350 func TestConcurrentWrites(t *testing.T) { 351 file, err := os.CreateTemp("", "") 352 if err != nil { 353 t.Fatal(err) 354 } 355 defer os.Remove(file.Name()) 356 357 err = Build(file.Name(), nil) 358 if err != nil { 359 t.Fatal(err) 360 } 361 362 if err := Add(file.Name(), []Record{ 363 { 364 Hosts: "inithostname", 365 IP: "172.17.0.1", 366 }, 367 }); err != nil { 368 t.Fatal(err) 369 } 370 371 group := new(errgroup.Group) 372 for i := 0; i < 10; i++ { 373 i := i 374 group.Go(func() error { 375 rec := []Record{ 376 { 377 IP: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i), 378 Hosts: fmt.Sprintf("testhostname%d", i), 379 }, 380 } 381 382 for j := 0; j < 25; j++ { 383 if err := Add(file.Name(), rec); err != nil { 384 return err 385 } 386 387 if err := Delete(file.Name(), rec); err != nil { 388 return err 389 } 390 } 391 return nil 392 }) 393 } 394 395 if err := group.Wait(); err != nil { 396 t.Fatal(err) 397 } 398 399 content, err := os.ReadFile(file.Name()) 400 if err != nil { 401 t.Fatal(err) 402 } 403 404 if expected := "172.17.0.1\tinithostname\n"; !bytes.Contains(content, []byte(expected)) { 405 t.Fatalf("Expected to find '%s' got '%s'", expected, content) 406 } 407 } 408 409 func benchDelete(b *testing.B) { 410 b.StopTimer() 411 file, err := os.CreateTemp("", "") 412 if err != nil { 413 b.Fatal(err) 414 } 415 defer func() { 416 b.StopTimer() 417 file.Close() 418 os.Remove(file.Name()) 419 b.StartTimer() 420 }() 421 422 err = Build(file.Name(), nil) 423 if err != nil { 424 b.Fatal(err) 425 } 426 427 var records []Record 428 var toDelete []Record 429 for i := 0; i < 255; i++ { 430 record := Record{ 431 Hosts: fmt.Sprintf("testhostname%d", i), 432 IP: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i), 433 } 434 records = append(records, record) 435 if i%2 == 0 { 436 toDelete = append(records, record) 437 } 438 } 439 440 if err := Add(file.Name(), records); err != nil { 441 b.Fatal(err) 442 } 443 444 b.StartTimer() 445 if err := Delete(file.Name(), toDelete); err != nil { 446 b.Fatal(err) 447 } 448 } 449 450 func BenchmarkDelete(b *testing.B) { 451 for i := 0; i < b.N; i++ { 452 benchDelete(b) 453 } 454 }