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