4
4
import subprocess
5
5
import time
6
6
import timeit
7
+ from typing import Any , Dict , Iterable , Iterator , List , Tuple , Union
7
8
8
9
import attr
9
-
10
10
import pytest
11
+ from _pytest .config import Config
12
+ from _pytest .fixtures import FixtureRequest
11
13
12
14
13
15
@pytest .fixture
14
- def container_scope_fixture (request ) :
16
+ def container_scope_fixture (request : FixtureRequest ) -> Any :
15
17
return request .config .getoption ("--container-scope" )
16
18
17
- def containers_scope (fixture_name , config ):
19
+
20
+ def containers_scope (fixture_name : str , config : Config ) -> Any : # pylint: disable=unused-argument
18
21
return config .getoption ("--container-scope" , "session" )
19
22
20
- def execute (command , success_codes = (0 ,)):
23
+
24
+ def execute (command : str , success_codes : Iterable [int ] = (0 ,)) -> Union [bytes , Any ]:
21
25
"""Run a shell command."""
22
26
try :
23
27
output = subprocess .check_output (command , stderr = subprocess .STDOUT , shell = True )
@@ -29,14 +33,12 @@ def execute(command, success_codes=(0,)):
29
33
30
34
if status not in success_codes :
31
35
raise Exception (
32
- 'Command {} returned {}: """{}""".' .format (
33
- command , status , output .decode ("utf-8" )
34
- )
36
+ 'Command {} returned {}: """{}""".' .format (command , status , output .decode ("utf-8" ))
35
37
)
36
38
return output
37
39
38
40
39
- def get_docker_ip ():
41
+ def get_docker_ip () -> Union [ str , Any ] :
40
42
# When talking to the Docker daemon via a UNIX socket, route all TCP
41
43
# traffic to docker containers via the TCP loopback interface.
42
44
docker_host = os .environ .get ("DOCKER_HOST" , "" ).strip ()
@@ -50,19 +52,18 @@ def get_docker_ip():
50
52
51
53
52
54
@pytest .fixture (scope = containers_scope )
53
- def docker_ip ():
55
+ def docker_ip () -> Union [ str , Any ] :
54
56
"""Determine the IP address for TCP connections to Docker containers."""
55
57
56
58
return get_docker_ip ()
57
59
58
60
59
61
@attr .s (frozen = True )
60
62
class Services :
63
+ _docker_compose : Any = attr .ib ()
64
+ _services : Dict [Any , Dict [Any , Any ]] = attr .ib (init = False , default = attr .Factory (dict ))
61
65
62
- _docker_compose = attr .ib ()
63
- _services = attr .ib (init = False , default = attr .Factory (dict ))
64
-
65
- def port_for (self , service , container_port ):
66
+ def port_for (self , service : str , container_port : int ) -> int :
66
67
"""Return the "host" port for `service` and `container_port`.
67
68
68
69
E.g. If the service is defined like this:
@@ -78,16 +79,14 @@ def port_for(self, service, container_port):
78
79
"""
79
80
80
81
# Lookup in the cache.
81
- cache = self ._services .get (service , {}).get (container_port , None )
82
+ cache : int = self ._services .get (service , {}).get (container_port , None )
82
83
if cache is not None :
83
84
return cache
84
85
85
86
output = self ._docker_compose .execute ("port %s %d" % (service , container_port ))
86
87
endpoint = output .strip ().decode ("utf-8" )
87
88
if not endpoint :
88
- raise ValueError (
89
- 'Could not detect port for "%s:%d".' % (service , container_port )
90
- )
89
+ raise ValueError ('Could not detect port for "%s:%d".' % (service , container_port ))
91
90
92
91
# This handles messy output that might contain warnings or other text
93
92
if len (endpoint .split ("\n " )) > 1 :
@@ -101,7 +100,13 @@ def port_for(self, service, container_port):
101
100
102
101
return match
103
102
104
- def wait_until_responsive (self , check , timeout , pause , clock = timeit .default_timer ):
103
+ def wait_until_responsive (
104
+ self ,
105
+ check : Any ,
106
+ timeout : float ,
107
+ pause : float ,
108
+ clock : Any = timeit .default_timer ,
109
+ ) -> None :
105
110
"""Wait until a service is responsive."""
106
111
107
112
ref = clock ()
@@ -115,20 +120,19 @@ def wait_until_responsive(self, check, timeout, pause, clock=timeit.default_time
115
120
raise Exception ("Timeout reached while waiting on service!" )
116
121
117
122
118
- def str_to_list (arg ) :
123
+ def str_to_list (arg : Union [ str , List [ Any ], Tuple [ Any ]]) -> Union [ List [ Any ], Tuple [ Any ]] :
119
124
if isinstance (arg , (list , tuple )):
120
125
return arg
121
126
return [arg ]
122
127
123
128
124
129
@attr .s (frozen = True )
125
130
class DockerComposeExecutor :
131
+ _compose_command : str = attr .ib ()
132
+ _compose_files : Any = attr .ib (converter = str_to_list )
133
+ _compose_project_name : str = attr .ib ()
126
134
127
- _compose_command = attr .ib ()
128
- _compose_files = attr .ib (converter = str_to_list )
129
- _compose_project_name = attr .ib ()
130
-
131
- def execute (self , subcommand ):
135
+ def execute (self , subcommand : str ) -> Union [bytes , Any ]:
132
136
command = self ._compose_command
133
137
for compose_file in self ._compose_files :
134
138
command += ' -f "{}"' .format (compose_file )
@@ -137,7 +141,7 @@ def execute(self, subcommand):
137
141
138
142
139
143
@pytest .fixture (scope = containers_scope )
140
- def docker_compose_command ():
144
+ def docker_compose_command () -> str :
141
145
"""Docker Compose command to use, it could be either `docker compose`
142
146
for Docker Compose V2 or `docker-compose` for Docker Compose
143
147
V1."""
@@ -146,40 +150,40 @@ def docker_compose_command():
146
150
147
151
148
152
@pytest .fixture (scope = containers_scope )
149
- def docker_compose_file (pytestconfig ) :
153
+ def docker_compose_file (pytestconfig : Any ) -> str :
150
154
"""Get an absolute path to the `docker-compose.yml` file. Override this
151
155
fixture in your tests if you need a custom location."""
152
156
153
157
return os .path .join (str (pytestconfig .rootdir ), "tests" , "docker-compose.yml" )
154
158
155
159
156
160
@pytest .fixture (scope = containers_scope )
157
- def docker_compose_project_name ():
161
+ def docker_compose_project_name () -> str :
158
162
"""Generate a project name using the current process PID. Override this
159
163
fixture in your tests if you need a particular project name."""
160
164
161
165
return "pytest{}" .format (os .getpid ())
162
166
163
167
164
- def get_cleanup_command ():
168
+ def get_cleanup_command () -> Union [ List [ str ], str ] :
165
169
return ["down -v" ]
166
170
167
171
168
172
@pytest .fixture (scope = containers_scope )
169
- def docker_cleanup ():
173
+ def docker_cleanup () -> Union [ List [ str ], str ] :
170
174
"""Get the docker_compose command to be executed for test clean-up actions.
171
175
Override this fixture in your tests if you need to change clean-up actions.
172
176
Returning anything that would evaluate to False will skip this command."""
173
177
174
178
return get_cleanup_command ()
175
179
176
180
177
- def get_setup_command ():
181
+ def get_setup_command () -> Union [ List [ str ], str ] :
178
182
return ["up --build -d" ]
179
183
180
184
181
185
@pytest .fixture (scope = containers_scope )
182
- def docker_setup ():
186
+ def docker_setup () -> Union [ List [ str ], str ] :
183
187
"""Get the docker_compose command to be executed for test setup actions.
184
188
Override this fixture in your tests if you need to change setup actions.
185
189
Returning anything that would evaluate to False will skip this command."""
@@ -189,12 +193,12 @@ def docker_setup():
189
193
190
194
@contextlib .contextmanager
191
195
def get_docker_services (
192
- docker_compose_command ,
193
- docker_compose_file ,
194
- docker_compose_project_name ,
195
- docker_setup ,
196
- docker_cleanup ,
197
- ):
196
+ docker_compose_command : str ,
197
+ docker_compose_file : str ,
198
+ docker_compose_project_name : str ,
199
+ docker_setup : Union [ List [ str ], str ] ,
200
+ docker_cleanup : Union [ List [ str ], str ] ,
201
+ ) -> Iterator [ Services ] :
198
202
docker_compose = DockerComposeExecutor (
199
203
docker_compose_command , docker_compose_file , docker_compose_project_name
200
204
)
@@ -222,12 +226,12 @@ def get_docker_services(
222
226
223
227
@pytest .fixture (scope = containers_scope )
224
228
def docker_services (
225
- docker_compose_command ,
226
- docker_compose_file ,
227
- docker_compose_project_name ,
228
- docker_setup ,
229
- docker_cleanup ,
230
- ):
229
+ docker_compose_command : str ,
230
+ docker_compose_file : str ,
231
+ docker_compose_project_name : str ,
232
+ docker_setup : str ,
233
+ docker_cleanup : str ,
234
+ ) -> Iterator [ Services ] :
231
235
"""Start all services from a docker compose file (`docker-compose up`).
232
236
After test are finished, shutdown all services (`docker-compose down`)."""
233
237
0 commit comments