1package testcontainers 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/http" 8 "path/filepath" 9 "testing" 10 "time" 11 12 "github.com/docker/docker/errdefs" 13 14 "github.com/docker/docker/api/types/volume" 15 16 "database/sql" 17 // Import mysql into the scope of this package (required) 18 _ "github.com/go-sql-driver/mysql" 19 20 "github.com/docker/docker/api/types" 21 "github.com/docker/docker/api/types/filters" 22 "github.com/docker/docker/client" 23 "github.com/docker/go-connections/nat" 24 "github.com/go-redis/redis" 25 "github.com/testcontainers/testcontainers-go/wait" 26) 27 28func TestContainerAttachedToNewNetwork(t *testing.T) { 29 networkName := "new-network" 30 31 ctx := context.Background() 32 gcr := GenericContainerRequest{ 33 ContainerRequest: ContainerRequest{ 34 Image: "nginx", 35 ExposedPorts: []string{ 36 "80/tcp", 37 }, 38 Networks: []string{ 39 networkName, 40 }, 41 NetworkAliases: map[string][]string{ 42 networkName: { 43 "alias1", "alias2", "alias3", 44 }, 45 }, 46 }, 47 } 48 49 provider, err := gcr.ProviderType.GetProvider() 50 51 newNetwork, err := provider.CreateNetwork(ctx, NetworkRequest{ 52 Name: networkName, 53 CheckDuplicate: true, 54 }) 55 if err != nil { 56 t.Fatal(err) 57 } 58 defer newNetwork.Remove(ctx) 59 60 nginx, err := GenericContainer(ctx, gcr) 61 if err != nil { 62 t.Fatal(err) 63 } 64 defer nginx.Terminate(ctx) 65 66 networks, err := nginx.Networks(ctx) 67 if err != nil { 68 t.Fatal(err) 69 } 70 if len(networks) != 1 { 71 t.Errorf("Expected networks 1. Got '%d'.", len(networks)) 72 } 73 network := networks[0] 74 if network != networkName { 75 t.Errorf("Expected network name '%s'. Got '%s'.", networkName, network) 76 } 77 78 networkAliases, err := nginx.NetworkAliases(ctx) 79 if err != nil { 80 t.Fatal(err) 81 } 82 if len(networkAliases) != 1 { 83 t.Errorf("Expected network aliases for 1 network. Got '%d'.", len(networkAliases)) 84 } 85 networkAlias := networkAliases[networkName] 86 if len(networkAlias) != 3 { 87 t.Errorf("Expected network aliases %d. Got '%d'.", 3, len(networkAlias)) 88 } 89 if networkAlias[0] != "alias1" || networkAlias[1] != "alias2" || networkAlias[2] != "alias3" { 90 t.Errorf( 91 "Expected network aliases '%s', '%s' and '%s'. Got '%s', '%s' and '%s'.", 92 "alias1", "alias2", "alias3", networkAlias[0], networkAlias[1], networkAlias[2]) 93 } 94} 95 96func TestContainerWithHostNetworkOptions(t *testing.T) { 97 ctx := context.Background() 98 gcr := GenericContainerRequest{ 99 ContainerRequest: ContainerRequest{ 100 Image: "nginx", 101 SkipReaper: true, 102 NetworkMode: "host", 103 }, 104 Started: true, 105 } 106 107 nginxC, err := GenericContainer(ctx, gcr) 108 if err != nil { 109 t.Fatal(err) 110 } 111 112 defer nginxC.Terminate(ctx) 113 114 host, err := nginxC.Host(ctx) 115 if err != nil { 116 t.Errorf("Expected host %s. Got '%d'.", host, err) 117 } 118 119 _, err = http.Get("http://" + host + ":80") 120 if err != nil { 121 t.Errorf("Expected OK response. Got '%d'.", err) 122 } 123} 124 125func TestContainerWithNetworkModeAndNetworkTogether(t *testing.T) { 126 ctx := context.Background() 127 gcr := GenericContainerRequest{ 128 ContainerRequest: ContainerRequest{ 129 Image: "nginx", 130 SkipReaper: true, 131 NetworkMode: "host", 132 Networks: []string{"new-network"}, 133 }, 134 Started: true, 135 } 136 137 _, err := GenericContainer(ctx, gcr) 138 if err != nil { 139 // Error when NetworkMode = host and Network = []string{"bridge"} 140 t.Logf("Can't use Network and NetworkMode together, %s", err) 141 } 142} 143 144func TestContainerWithHostNetworkOptionsAndWaitStrategy(t *testing.T) { 145 ctx := context.Background() 146 gcr := GenericContainerRequest{ 147 ContainerRequest: ContainerRequest{ 148 Image: "nginx", 149 SkipReaper: true, 150 NetworkMode: "host", 151 WaitingFor: wait.ForListeningPort("80/tcp"), 152 }, 153 Started: true, 154 } 155 156 nginxC, err := GenericContainer(ctx, gcr) 157 if err != nil { 158 t.Fatal(err) 159 } 160 161 defer nginxC.Terminate(ctx) 162 163 host, err := nginxC.Host(ctx) 164 if err != nil { 165 t.Errorf("Expected host %s. Got '%d'.", host, err) 166 } 167 168 _, err = http.Get("http://" + host + ":80") 169 if err != nil { 170 t.Errorf("Expected OK response. Got '%d'.", err) 171 } 172} 173 174func TestContainerWithHostNetworkAndEndpoint(t *testing.T) { 175 nginxPort := "80/tcp" 176 ctx := context.Background() 177 gcr := GenericContainerRequest{ 178 ContainerRequest: ContainerRequest{ 179 Image: "nginx", 180 SkipReaper: true, 181 NetworkMode: "host", 182 WaitingFor: wait.ForListeningPort(nat.Port(nginxPort)), 183 }, 184 Started: true, 185 } 186 187 nginxC, err := GenericContainer(ctx, gcr) 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 defer nginxC.Terminate(ctx) 193 194 hostN, err := nginxC.Endpoint(ctx, "") 195 if err != nil { 196 t.Errorf("Expected host %s. Got '%d'.", hostN, err) 197 } 198 t.Log(hostN) 199 200 _, err = http.Get("http://" + hostN) 201 if err != nil { 202 t.Errorf("Expected OK response. Got '%d'.", err) 203 } 204} 205 206func TestContainerWithHostNetworkAndPortEndpoint(t *testing.T) { 207 nginxPort := "80/tcp" 208 ctx := context.Background() 209 gcr := GenericContainerRequest{ 210 ContainerRequest: ContainerRequest{ 211 Image: "nginx", 212 SkipReaper: true, 213 NetworkMode: "host", 214 WaitingFor: wait.ForListeningPort(nat.Port(nginxPort)), 215 }, 216 Started: true, 217 } 218 219 nginxC, err := GenericContainer(ctx, gcr) 220 if err != nil { 221 t.Fatal(err) 222 } 223 224 defer nginxC.Terminate(ctx) 225 226 origin, err := nginxC.PortEndpoint(ctx, nat.Port(nginxPort), "http") 227 if err != nil { 228 t.Errorf("Expected host %s. Got '%d'.", origin, err) 229 } 230 t.Log(origin) 231 232 _, err = http.Get(origin) 233 if err != nil { 234 t.Errorf("Expected OK response. Got '%d'.", err) 235 } 236} 237 238func TestContainerReturnItsContainerID(t *testing.T) { 239 ctx := context.Background() 240 nginxA, err := GenericContainer(ctx, GenericContainerRequest{ 241 ContainerRequest: ContainerRequest{ 242 Image: "nginx", 243 ExposedPorts: []string{ 244 "80/tcp", 245 }, 246 }, 247 }) 248 if err != nil { 249 t.Fatal(err) 250 } 251 defer nginxA.Terminate(ctx) 252 if nginxA.GetContainerID() == "" { 253 t.Errorf("expected a containerID but we got an empty string.") 254 } 255} 256 257func TestContainerStartsWithoutTheReaper(t *testing.T) { 258 t.Skip("need to use the sessionID") 259 ctx := context.Background() 260 client, err := client.NewEnvClient() 261 if err != nil { 262 t.Fatal(err) 263 } 264 client.NegotiateAPIVersion(ctx) 265 _, err = GenericContainer(ctx, GenericContainerRequest{ 266 ContainerRequest: ContainerRequest{ 267 Image: "nginx", 268 ExposedPorts: []string{ 269 "80/tcp", 270 }, 271 SkipReaper: true, 272 }, 273 Started: true, 274 }) 275 if err != nil { 276 t.Fatal(err) 277 } 278 filtersJSON := fmt.Sprintf(`{"label":{"%s":true}}`, TestcontainerLabelIsReaper) 279 f, err := filters.FromJSON(filtersJSON) 280 if err != nil { 281 t.Fatal(err) 282 } 283 resp, err := client.ContainerList(ctx, types.ContainerListOptions{ 284 Filters: f, 285 }) 286 if err != nil { 287 t.Fatal(err) 288 } 289 if len(resp) != 0 { 290 t.Fatal("expected zero reaper running.") 291 } 292} 293 294func TestContainerStartsWithTheReaper(t *testing.T) { 295 ctx := context.Background() 296 client, err := client.NewEnvClient() 297 if err != nil { 298 t.Fatal(err) 299 } 300 client.NegotiateAPIVersion(ctx) 301 _, err = GenericContainer(ctx, GenericContainerRequest{ 302 ContainerRequest: ContainerRequest{ 303 Image: "nginx", 304 ExposedPorts: []string{ 305 "80/tcp", 306 }, 307 }, 308 Started: true, 309 }) 310 if err != nil { 311 t.Fatal(err) 312 } 313 filtersJSON := fmt.Sprintf(`{"label":{"%s":true}}`, TestcontainerLabelIsReaper) 314 f, err := filters.FromJSON(filtersJSON) 315 if err != nil { 316 t.Fatal(err) 317 } 318 resp, err := client.ContainerList(ctx, types.ContainerListOptions{ 319 Filters: f, 320 }) 321 if err != nil { 322 t.Fatal(err) 323 } 324 if len(resp) == 0 { 325 t.Fatal("expected at least one reaper to be running.") 326 } 327} 328 329func TestContainerTerminationResetsState(t *testing.T) { 330 ctx := context.Background() 331 client, err := client.NewEnvClient() 332 if err != nil { 333 t.Fatal(err) 334 } 335 client.NegotiateAPIVersion(ctx) 336 nginxA, err := GenericContainer(ctx, GenericContainerRequest{ 337 ContainerRequest: ContainerRequest{ 338 Image: "nginx", 339 ExposedPorts: []string{ 340 "80/tcp", 341 }, 342 SkipReaper: true, 343 }, 344 Started: true, 345 }) 346 if err != nil { 347 t.Fatal(err) 348 } 349 350 err = nginxA.Terminate(ctx) 351 if err != nil { 352 t.Fatal(err) 353 } 354 if nginxA.SessionID() != "00000000-0000-0000-0000-000000000000" { 355 t.Fatal("Internal state must be reset.") 356 } 357 ports, err := nginxA.Ports(ctx) 358 if err == nil || ports != nil { 359 t.Fatal("expected error from container inspect.") 360 } 361} 362 363func TestContainerTerminationWithReaper(t *testing.T) { 364 ctx := context.Background() 365 client, err := client.NewEnvClient() 366 if err != nil { 367 t.Fatal(err) 368 } 369 client.NegotiateAPIVersion(ctx) 370 nginxA, err := GenericContainer(ctx, GenericContainerRequest{ 371 ContainerRequest: ContainerRequest{ 372 Image: "nginx", 373 ExposedPorts: []string{ 374 "80/tcp", 375 }, 376 }, 377 Started: true, 378 }) 379 if err != nil { 380 t.Fatal(err) 381 } 382 containerID := nginxA.GetContainerID() 383 resp, err := client.ContainerInspect(ctx, containerID) 384 if err != nil { 385 t.Fatal(err) 386 } 387 if resp.State.Running != true { 388 t.Fatal("The container shoud be in running state") 389 } 390 err = nginxA.Terminate(ctx) 391 if err != nil { 392 t.Fatal(err) 393 } 394 _, err = client.ContainerInspect(ctx, containerID) 395 if err == nil { 396 t.Fatal("expected error from container inspect.") 397 } 398} 399 400func TestContainerTerminationWithoutReaper(t *testing.T) { 401 ctx := context.Background() 402 client, err := client.NewEnvClient() 403 if err != nil { 404 t.Fatal(err) 405 } 406 client.NegotiateAPIVersion(ctx) 407 nginxA, err := GenericContainer(ctx, GenericContainerRequest{ 408 ContainerRequest: ContainerRequest{ 409 Image: "nginx", 410 ExposedPorts: []string{ 411 "80/tcp", 412 }, 413 SkipReaper: true, 414 }, 415 Started: true, 416 }) 417 if err != nil { 418 t.Fatal(err) 419 } 420 containerID := nginxA.GetContainerID() 421 resp, err := client.ContainerInspect(ctx, containerID) 422 if err != nil { 423 t.Fatal(err) 424 } 425 if resp.State.Running != true { 426 t.Fatal("The container shoud be in running state") 427 } 428 err = nginxA.Terminate(ctx) 429 if err != nil { 430 t.Fatal(err) 431 } 432 _, err = client.ContainerInspect(ctx, containerID) 433 if err == nil { 434 t.Fatal("expected error from container inspect.") 435 } 436} 437 438func TestTwoContainersExposingTheSamePort(t *testing.T) { 439 ctx := context.Background() 440 nginxA, err := GenericContainer(ctx, GenericContainerRequest{ 441 ContainerRequest: ContainerRequest{ 442 Image: "nginx", 443 ExposedPorts: []string{ 444 "80/tcp", 445 }, 446 }, 447 Started: true, 448 }) 449 if err != nil { 450 t.Fatal(err) 451 } 452 defer func() { 453 err := nginxA.Terminate(ctx) 454 if err != nil { 455 t.Fatal(err) 456 } 457 }() 458 459 nginxB, err := GenericContainer(ctx, GenericContainerRequest{ 460 ContainerRequest: ContainerRequest{ 461 Image: "nginx", 462 ExposedPorts: []string{ 463 "80/tcp", 464 }, 465 }, 466 Started: true, 467 }) 468 if err != nil { 469 t.Fatal(err) 470 } 471 defer func() { 472 err := nginxB.Terminate(ctx) 473 if err != nil { 474 t.Fatal(err) 475 } 476 }() 477 478 ipA, err := nginxA.Host(ctx) 479 if err != nil { 480 t.Fatal(err) 481 } 482 portA, err := nginxA.MappedPort(ctx, "80/tcp") 483 if err != nil { 484 t.Fatal(err) 485 } 486 resp, err := http.Get(fmt.Sprintf("http://%s:%s", ipA, portA.Port())) 487 if err != nil { 488 t.Fatal(err) 489 } 490 if resp.StatusCode != http.StatusOK { 491 t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) 492 } 493 494 ipB, err := nginxB.Host(ctx) 495 if err != nil { 496 t.Fatal(err) 497 } 498 portB, err := nginxB.MappedPort(ctx, "80") 499 if err != nil { 500 t.Fatal(err) 501 } 502 503 resp, err = http.Get(fmt.Sprintf("http://%s:%s", ipB, portB.Port())) 504 if err != nil { 505 t.Fatal(err) 506 } 507 if resp.StatusCode != http.StatusOK { 508 t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) 509 } 510} 511 512func TestContainerCreation(t *testing.T) { 513 ctx := context.Background() 514 515 nginxPort := "80/tcp" 516 nginxC, err := GenericContainer(ctx, GenericContainerRequest{ 517 ContainerRequest: ContainerRequest{ 518 Image: "nginx", 519 ExposedPorts: []string{ 520 nginxPort, 521 }, 522 }, 523 Started: true, 524 }) 525 if err != nil { 526 t.Fatal(err) 527 } 528 defer func() { 529 err := nginxC.Terminate(ctx) 530 if err != nil { 531 t.Fatal(err) 532 } 533 }() 534 ip, err := nginxC.Host(ctx) 535 if err != nil { 536 t.Fatal(err) 537 } 538 port, err := nginxC.MappedPort(ctx, "80") 539 if err != nil { 540 t.Fatal(err) 541 } 542 resp, err := http.Get(fmt.Sprintf("http://%s:%s", ip, port.Port())) 543 if err != nil { 544 t.Fatal(err) 545 } 546 if resp.StatusCode != http.StatusOK { 547 t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) 548 } 549 networkAliases, err := nginxC.NetworkAliases(ctx) 550 if err != nil { 551 t.Fatal(err) 552 } 553 if len(networkAliases) != 1 { 554 fmt.Printf("%v", networkAliases) 555 t.Errorf("Expected number of connected networks %d. Got %d.", 0, len(networkAliases)) 556 } 557 if len(networkAliases["bridge"]) != 0 { 558 t.Errorf("Expected number of aliases for 'bridge' network %d. Got %d.", 0, len(networkAliases["bridge"])) 559 } 560} 561 562func TestContainerCreationWithName(t *testing.T) { 563 ctx := context.Background() 564 565 creationName := fmt.Sprintf("%s_%d", "test_container", time.Now().Unix()) 566 expectedName := "/" + creationName // inspect adds '/' in the beginning 567 nginxPort := "80/tcp" 568 nginxC, err := GenericContainer(ctx, GenericContainerRequest{ 569 ContainerRequest: ContainerRequest{ 570 Image: "nginx", 571 ExposedPorts: []string{ 572 nginxPort, 573 }, 574 Name: creationName, 575 Networks: []string{"bridge"}, 576 }, 577 Started: true, 578 }) 579 if err != nil { 580 t.Fatal(err) 581 } 582 defer func() { 583 err := nginxC.Terminate(ctx) 584 if err != nil { 585 t.Fatal(err) 586 } 587 }() 588 name, err := nginxC.Name(ctx) 589 if err != nil { 590 t.Fatal(err) 591 } 592 if name != expectedName { 593 t.Errorf("Expected container name '%s'. Got '%s'.", expectedName, name) 594 } 595 networks, err := nginxC.Networks(ctx) 596 if err != nil { 597 t.Fatal(err) 598 } 599 if len(networks) != 1 { 600 t.Errorf("Expected networks 1. Got '%d'.", len(networks)) 601 } 602 network := networks[0] 603 if network != "bridge" { 604 t.Errorf("Expected network name '%s'. Got '%s'.", "bridge", network) 605 } 606 ip, err := nginxC.Host(ctx) 607 if err != nil { 608 t.Fatal(err) 609 } 610 port, err := nginxC.MappedPort(ctx, "80") 611 if err != nil { 612 t.Fatal(err) 613 } 614 resp, err := http.Get(fmt.Sprintf("http://%s:%s", ip, port.Port())) 615 if err != nil { 616 t.Fatal(err) 617 } 618 if resp.StatusCode != http.StatusOK { 619 t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) 620 } 621} 622 623func TestContainerCreationAndWaitForListeningPortLongEnough(t *testing.T) { 624 ctx := context.Background() 625 626 nginxPort := "80/tcp" 627 // delayed-nginx will wait 2s before opening port 628 nginxC, err := GenericContainer(ctx, GenericContainerRequest{ 629 ContainerRequest: ContainerRequest{ 630 Image: "menedev/delayed-nginx:1.15.2", 631 ExposedPorts: []string{ 632 nginxPort, 633 }, 634 WaitingFor: wait.ForListeningPort("80"), // default startupTimeout is 60s 635 }, 636 Started: true, 637 }) 638 if err != nil { 639 t.Fatal(err) 640 } 641 defer func() { 642 err := nginxC.Terminate(ctx) 643 if err != nil { 644 t.Fatal(err) 645 } 646 }() 647 origin, err := nginxC.PortEndpoint(ctx, nat.Port(nginxPort), "http") 648 if err != nil { 649 t.Fatal(err) 650 } 651 resp, err := http.Get(origin) 652 if err != nil { 653 t.Fatal(err) 654 } 655 if resp.StatusCode != http.StatusOK { 656 t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) 657 } 658} 659 660func TestContainerCreationTimesOut(t *testing.T) { 661 t.Skip("Wait needs to be fixed") 662 ctx := context.Background() 663 // delayed-nginx will wait 2s before opening port 664 nginxC, err := GenericContainer(ctx, GenericContainerRequest{ 665 ContainerRequest: ContainerRequest{ 666 Image: "menedev/delayed-nginx:1.15.2", 667 ExposedPorts: []string{ 668 "80/tcp", 669 }, 670 WaitingFor: wait.ForListeningPort("80").WithStartupTimeout(1 * time.Second), 671 }, 672 Started: true, 673 }) 674 if err == nil { 675 t.Error("Expected timeout") 676 err := nginxC.Terminate(ctx) 677 if err != nil { 678 t.Fatal(err) 679 } 680 } 681} 682 683func TestContainerRespondsWithHttp200ForIndex(t *testing.T) { 684 t.Skip("Wait needs to be fixed") 685 ctx := context.Background() 686 687 nginxPort := "80/tcp" 688 // delayed-nginx will wait 2s before opening port 689 nginxC, err := GenericContainer(ctx, GenericContainerRequest{ 690 ContainerRequest: ContainerRequest{ 691 Image: "nginx", 692 ExposedPorts: []string{ 693 nginxPort, 694 }, 695 WaitingFor: wait.ForHTTP("/"), 696 }, 697 Started: true, 698 }) 699 if err != nil { 700 t.Fatal(err) 701 } 702 defer func() { 703 err := nginxC.Terminate(ctx) 704 if err != nil { 705 t.Fatal(err) 706 } 707 }() 708 709 origin, err := nginxC.PortEndpoint(ctx, nat.Port(nginxPort), "http") 710 if err != nil { 711 t.Fatal(err) 712 } 713 resp, err := http.Get(origin) 714 if err != nil { 715 t.Error(err) 716 } 717 if resp.StatusCode != http.StatusOK { 718 t.Errorf("Expected status code %d. Got %d.", http.StatusOK, resp.StatusCode) 719 } 720} 721 722func TestContainerCreationTimesOutWithHttp(t *testing.T) { 723 t.Skip("Wait needs to be fixed") 724 ctx := context.Background() 725 // delayed-nginx will wait 2s before opening port 726 nginxC, err := GenericContainer(ctx, GenericContainerRequest{ 727 ContainerRequest: ContainerRequest{ 728 Image: "menedev/delayed-nginx:1.15.2", 729 ExposedPorts: []string{ 730 "80/tcp", 731 }, 732 WaitingFor: wait.ForHTTP("/").WithStartupTimeout(1 * time.Second), 733 }, 734 Started: true, 735 }) 736 defer func() { 737 err := nginxC.Terminate(ctx) 738 if err != nil { 739 t.Fatal(err) 740 } 741 }() 742 743 if err == nil { 744 t.Error("Expected timeout") 745 } 746} 747 748func TestContainerCreationWaitsForLogContextTimeout(t *testing.T) { 749 ctx := context.Background() 750 req := ContainerRequest{ 751 Image: "mysql:latest", 752 ExposedPorts: []string{"3306/tcp", "33060/tcp"}, 753 Env: map[string]string{ 754 "MYSQL_ROOT_PASSWORD": "password", 755 "MYSQL_DATABASE": "database", 756 }, 757 WaitingFor: wait.ForLog("test context timeout").WithStartupTimeout(1 * time.Second), 758 } 759 _, err := GenericContainer(ctx, GenericContainerRequest{ 760 ContainerRequest: req, 761 Started: true, 762 }) 763 764 if err == nil { 765 t.Error("Expected timeout") 766 } 767} 768 769func TestContainerCreationWaitsForLog(t *testing.T) { 770 ctx := context.Background() 771 req := ContainerRequest{ 772 Image: "mysql:latest", 773 ExposedPorts: []string{"3306/tcp", "33060/tcp"}, 774 Env: map[string]string{ 775 "MYSQL_ROOT_PASSWORD": "password", 776 "MYSQL_DATABASE": "database", 777 }, 778 WaitingFor: wait.ForLog("port: 3306 MySQL Community Server - GPL"), 779 } 780 mysqlC, _ := GenericContainer(ctx, GenericContainerRequest{ 781 ContainerRequest: req, 782 Started: true, 783 }) 784 defer func() { 785 t.Log("terminating container") 786 err := mysqlC.Terminate(ctx) 787 if err != nil { 788 t.Fatal(err) 789 } 790 }() 791 792 host, _ := mysqlC.Host(ctx) 793 p, _ := mysqlC.MappedPort(ctx, "3306/tcp") 794 port := p.Int() 795 connectionString := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=skip-verify", 796 "root", "password", host, port, "database") 797 798 db, err := sql.Open("mysql", connectionString) 799 defer db.Close() 800 801 if err = db.Ping(); err != nil { 802 t.Errorf("error pinging db: %+v\n", err) 803 } 804 _, err = db.Exec("CREATE TABLE IF NOT EXISTS a_table ( \n" + 805 " `col_1` VARCHAR(128) NOT NULL, \n" + 806 " `col_2` VARCHAR(128) NOT NULL, \n" + 807 " PRIMARY KEY (`col_1`, `col_2`) \n" + 808 ")") 809 if err != nil { 810 t.Errorf("error creating table: %+v\n", err) 811 } 812} 813 814func Test_BuildContainerFromDockerfile(t *testing.T) { 815 t.Log("getting context") 816 context := context.Background() 817 t.Log("got context, creating container request") 818 req := ContainerRequest{ 819 FromDockerfile: FromDockerfile{ 820 Context: "./testresources", 821 }, 822 ExposedPorts: []string{"6379/tcp"}, 823 WaitingFor: wait.ForLog("Ready to accept connections"), 824 } 825 826 t.Log("creating generic container request from container request") 827 828 genContainerReq := GenericContainerRequest{ 829 ContainerRequest: req, 830 Started: true, 831 } 832 833 t.Log("creating redis container") 834 835 redisC, err := GenericContainer(context, genContainerReq) 836 837 t.Log("created redis container") 838 839 defer func() { 840 t.Log("terminating redis container") 841 err := redisC.Terminate(context) 842 if err != nil { 843 t.Fatal(err) 844 } 845 t.Log("terminated redis container") 846 }() 847 848 t.Log("getting redis container endpoint") 849 endpoint, err := redisC.Endpoint(context, "") 850 if err != nil { 851 t.Fatal(err) 852 } 853 854 t.Log("retrieved redis container endpoint") 855 856 client := redis.NewClient(&redis.Options{ 857 Addr: endpoint, 858 }) 859 860 t.Log("pinging redis") 861 pong, err := client.Ping().Result() 862 863 t.Log("received response from redis") 864 865 if pong != "PONG" { 866 t.Fatalf("received unexpected response from redis: %s", pong) 867 } 868} 869 870func TestContainerCreationWaitsForLogAndPortContextTimeout(t *testing.T) { 871 ctx := context.Background() 872 req := ContainerRequest{ 873 Image: "mysql:latest", 874 ExposedPorts: []string{"3306/tcp", "33060/tcp"}, 875 Env: map[string]string{ 876 "MYSQL_ROOT_PASSWORD": "password", 877 "MYSQL_DATABASE": "database", 878 }, 879 WaitingFor: wait.ForAll( 880 wait.ForLog("I love testcontainers-go"), 881 wait.ForListeningPort("3306/tcp"), 882 ), 883 } 884 _, err := GenericContainer(ctx, GenericContainerRequest{ 885 ContainerRequest: req, 886 Started: true, 887 }) 888 889 if err == nil { 890 t.Fatal("Expected timeout") 891 } 892 893} 894 895func TestContainerCreationWaitingForHostPort(t *testing.T) { 896 ctx := context.Background() 897 req := ContainerRequest{ 898 Image: "nginx:1.17.6", 899 ExposedPorts: []string{"80/tcp"}, 900 WaitingFor: wait.ForListeningPort("80/tcp"), 901 } 902 nginx, err := GenericContainer(ctx, GenericContainerRequest{ 903 ContainerRequest: req, 904 Started: true, 905 }) 906 defer func() { 907 err := nginx.Terminate(ctx) 908 if err != nil { 909 t.Fatal(err) 910 } 911 t.Log("terminated nginx container") 912 }() 913 if err != nil { 914 t.Fatal(err) 915 } 916} 917 918func TestContainerCreationWaitingForHostPortWithoutBashThrowsAnError(t *testing.T) { 919 ctx := context.Background() 920 req := ContainerRequest{ 921 Image: "nginx:1.17.6-alpine", 922 ExposedPorts: []string{"80/tcp"}, 923 WaitingFor: wait.ForListeningPort("80/tcp"), 924 } 925 nginx, err := GenericContainer(ctx, GenericContainerRequest{ 926 ContainerRequest: req, 927 Started: true, 928 }) 929 defer func() { 930 err := nginx.Terminate(ctx) 931 if err != nil { 932 t.Fatal(err) 933 } 934 t.Log("terminated nginx container") 935 }() 936 if err != nil { 937 t.Fatal(err) 938 } 939} 940 941func TestContainerCreationWaitsForLogAndPort(t *testing.T) { 942 ctx := context.Background() 943 req := ContainerRequest{ 944 Image: "mysql:latest", 945 ExposedPorts: []string{"3306/tcp", "33060/tcp"}, 946 Env: map[string]string{ 947 "MYSQL_ROOT_PASSWORD": "password", 948 "MYSQL_DATABASE": "database", 949 }, 950 WaitingFor: wait.ForAll( 951 wait.ForLog("port: 3306 MySQL Community Server - GPL"), 952 wait.ForListeningPort("3306/tcp"), 953 ), 954 } 955 956 mysqlC, err := GenericContainer(ctx, GenericContainerRequest{ 957 ContainerRequest: req, 958 Started: true, 959 }) 960 961 if err != nil { 962 t.Fatal(err) 963 } 964 965 defer func() { 966 t.Log("terminating container") 967 err := mysqlC.Terminate(ctx) 968 if err != nil { 969 t.Fatal(err) 970 } 971 }() 972 973 host, _ := mysqlC.Host(ctx) 974 p, _ := mysqlC.MappedPort(ctx, "3306/tcp") 975 port := p.Int() 976 connectionString := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=skip-verify", 977 "root", "password", host, port, "database") 978 979 db, err := sql.Open("mysql", connectionString) 980 981 if err != nil { 982 t.Fatal(err) 983 } 984 985 defer db.Close() 986 987 if err = db.Ping(); err != nil { 988 t.Errorf("error pinging db: %+v\n", err) 989 } 990 991} 992 993func TestCMD(t *testing.T) { 994 /* 995 echo a unique statement to ensure that we 996 can pass in a command to the ContainerRequest 997 and it will be run when we run the container 998 */ 999 1000 ctx := context.Background() 1001 1002 req := ContainerRequest{ 1003 Image: "alpine", 1004 WaitingFor: wait.ForAll( 1005 wait.ForLog("command override!"), 1006 ), 1007 Cmd: []string{"echo", "command override!"}, 1008 } 1009 1010 c, err := GenericContainer(ctx, GenericContainerRequest{ 1011 ContainerRequest: req, 1012 Started: true, 1013 }) 1014 1015 if err != nil { 1016 t.Fatal(err) 1017 } 1018 1019 // defer not needed, but keeping it in for consistency 1020 defer c.Terminate(ctx) 1021} 1022 1023func ExampleDockerProvider_CreateContainer() { 1024 ctx := context.Background() 1025 req := ContainerRequest{ 1026 Image: "nginx", 1027 ExposedPorts: []string{"80/tcp"}, 1028 WaitingFor: wait.ForHTTP("/"), 1029 } 1030 nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ 1031 ContainerRequest: req, 1032 Started: true, 1033 }) 1034 defer nginxC.Terminate(ctx) 1035} 1036 1037func ExampleContainer_Host() { 1038 ctx := context.Background() 1039 req := ContainerRequest{ 1040 Image: "nginx", 1041 ExposedPorts: []string{"80/tcp"}, 1042 WaitingFor: wait.ForHTTP("/"), 1043 } 1044 nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ 1045 ContainerRequest: req, 1046 Started: true, 1047 }) 1048 defer nginxC.Terminate(ctx) 1049 ip, _ := nginxC.Host(ctx) 1050 println(ip) 1051} 1052 1053func ExampleContainer_Start() { 1054 ctx := context.Background() 1055 req := ContainerRequest{ 1056 Image: "nginx", 1057 ExposedPorts: []string{"80/tcp"}, 1058 WaitingFor: wait.ForHTTP("/"), 1059 } 1060 nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ 1061 ContainerRequest: req, 1062 }) 1063 defer nginxC.Terminate(ctx) 1064 nginxC.Start(ctx) 1065} 1066 1067func ExampleContainer_MappedPort() { 1068 ctx := context.Background() 1069 req := ContainerRequest{ 1070 Image: "nginx", 1071 ExposedPorts: []string{"80/tcp"}, 1072 WaitingFor: wait.ForHTTP("/"), 1073 } 1074 nginxC, _ := GenericContainer(ctx, GenericContainerRequest{ 1075 ContainerRequest: req, 1076 Started: true, 1077 }) 1078 defer nginxC.Terminate(ctx) 1079 ip, _ := nginxC.Host(ctx) 1080 port, _ := nginxC.MappedPort(ctx, "80") 1081 http.Get(fmt.Sprintf("http://%s:%s", ip, port.Port())) 1082} 1083 1084func TestContainerCreationWithBindAndVolume(t *testing.T) { 1085 absPath, err := filepath.Abs("./testresources/hello.sh") 1086 if err != nil { 1087 t.Fatal(err) 1088 } 1089 ctx, cnl := context.WithTimeout(context.Background(), 30*time.Second) 1090 defer cnl() 1091 // Create a Docker client. 1092 dockerCli, err := client.NewClientWithOpts(client.FromEnv) 1093 if err != nil { 1094 t.Fatal(err) 1095 } 1096 dockerCli.NegotiateAPIVersion(ctx) 1097 // Create the volume. 1098 vol, err := dockerCli.VolumeCreate(ctx, volume.VolumeCreateBody{ 1099 Driver: "local", 1100 }) 1101 if err != nil { 1102 t.Fatal(err) 1103 } 1104 volumeName := vol.Name 1105 defer func() { 1106 ctx, cnl := context.WithTimeout(context.Background(), 5*time.Second) 1107 defer cnl() 1108 err := dockerCli.VolumeRemove(ctx, volumeName, true) 1109 if err != nil { 1110 t.Fatal(err) 1111 } 1112 }() 1113 // Create the container that writes into the mounted volume. 1114 bashC, err := GenericContainer(ctx, GenericContainerRequest{ 1115 ContainerRequest: ContainerRequest{ 1116 Image: "bash", 1117 BindMounts: map[string]string{absPath: "/hello.sh"}, 1118 VolumeMounts: map[string]string{volumeName: "/data"}, 1119 Cmd: []string{"bash", "/hello.sh"}, 1120 WaitingFor: wait.ForLog("done"), 1121 }, 1122 Started: true, 1123 }) 1124 if err != nil { 1125 t.Fatal(err) 1126 } 1127 defer func() { 1128 ctx, cnl := context.WithTimeout(context.Background(), 5*time.Second) 1129 defer cnl() 1130 err := bashC.Terminate(ctx) 1131 if err != nil { 1132 t.Fatal(err) 1133 } 1134 }() 1135} 1136 1137func TestContainerWithTmpFs(t *testing.T) { 1138 ctx := context.Background() 1139 req := ContainerRequest{ 1140 Image: "busybox", 1141 Cmd: []string{"sleep", "10"}, 1142 Tmpfs: map[string]string{"/testtmpfs": "rw"}, 1143 } 1144 1145 container, err := GenericContainer(ctx, GenericContainerRequest{ 1146 ContainerRequest: req, 1147 Started: true, 1148 }) 1149 if err != nil { 1150 t.Fatal(err) 1151 } 1152 defer func() { 1153 t.Log("terminating container") 1154 err := container.Terminate(ctx) 1155 if err != nil { 1156 t.Fatal(err) 1157 } 1158 }() 1159 1160 var path = "/testtmpfs/test.file" 1161 1162 c, err := container.Exec(ctx, []string{"ls", path}) 1163 if err != nil { 1164 t.Fatal(err) 1165 } 1166 if c != 1 { 1167 t.Fatalf("File %s should not have existed, expected return code 1, got %v", path, c) 1168 } 1169 1170 c, err = container.Exec(ctx, []string{"touch", path}) 1171 if err != nil { 1172 t.Fatal(err) 1173 } 1174 if c != 0 { 1175 t.Fatalf("File %s should have been created successfully, expected return code 0, got %v", path, c) 1176 } 1177 1178 c, err = container.Exec(ctx, []string{"ls", path}) 1179 if err != nil { 1180 t.Fatal(err) 1181 } 1182 if c != 0 { 1183 t.Fatalf("File %s should exist, expected return code 0, got %v", path, c) 1184 } 1185} 1186 1187func TestContainerNonExistentImage(t *testing.T) { 1188 t.Run("if the image not found don't propagate the error", func(t *testing.T) { 1189 _, err := GenericContainer(context.Background(), GenericContainerRequest{ 1190 ContainerRequest: ContainerRequest{ 1191 Image: "postgres:nonexistent-version", 1192 SkipReaper: true, 1193 }, 1194 Started: true, 1195 }) 1196 1197 var nf errdefs.ErrNotFound 1198 if !errors.As(err, &nf) { 1199 t.Fatalf("the error should have bee an errdefs.ErrNotFound: %v", err) 1200 } 1201 }) 1202 1203 t.Run("the context cancellation is propagated to container creation", func(t *testing.T) { 1204 ctx, cancel := context.WithTimeout(context.Background(), time.Second) 1205 defer cancel() 1206 _, err := GenericContainer(ctx, GenericContainerRequest{ 1207 ContainerRequest: ContainerRequest{ 1208 Image: "postgres:latest", 1209 WaitingFor: wait.ForLog("log"), 1210 SkipReaper: true, 1211 }, 1212 Started: true, 1213 }) 1214 if !errors.Is(err, ctx.Err()) { 1215 t.Fatalf("err should be a ctx cancelled error %v", err) 1216 } 1217 }) 1218 1219} 1220