1
1
from pathlib import Path
2
2
import pytest
3
3
import subprocess
4
- from typing import Sequence
4
+ from typing import Optional , Sequence
5
5
from unittest import mock
6
6
7
7
from .const import ALT_DOCKER_IMAGE , DOCKER_IMAGE
8
8
9
9
import scuba .dockerutil as uut
10
10
11
11
12
+ def _mock_subprocess_run ( # type: ignore[no-untyped-def]
13
+ stdout : str ,
14
+ returncode : int = 0 ,
15
+ expected_args : Optional [Sequence [str ]] = None ,
16
+ ): # -> mock._patch[mock.MagicMock]:
17
+ def mocked_run (args , ** kwargs ): # type: ignore[no-untyped-def]
18
+ assert expected_args is None or args == expected_args
19
+ mock_obj = mock .MagicMock ()
20
+ mock_obj .returncode = returncode
21
+ mock_obj .stdout = stdout
22
+ return mock_obj
23
+
24
+ return mock .patch ("subprocess.run" , side_effect = mocked_run )
25
+
26
+
27
+ # -----------------------------------------------------------------------------
28
+ # get_image_command()
29
+
30
+
12
31
def test_get_image_command_success () -> None :
13
32
"""get_image_command works"""
14
33
assert uut .get_image_command (DOCKER_IMAGE )
@@ -33,14 +52,30 @@ def mocked_run(args, real_run=subprocess.run, **kw): # type: ignore[no-untyped-
33
52
uut .get_image_command ("n/a" )
34
53
35
54
36
- def _test_get_images (stdout : str , returncode : int = 0 ) -> Sequence [str ]:
37
- def mocked_run (* args , ** kwargs ): # type: ignore[no-untyped-def]
38
- mock_obj = mock .MagicMock ()
39
- mock_obj .returncode = returncode
40
- mock_obj .stdout = stdout
41
- return mock_obj
55
+ def test__get_image_command__pulls_image_if_missing () -> None :
56
+ """get_image_command pulls an image if missing"""
57
+ image = ALT_DOCKER_IMAGE
42
58
43
- with mock .patch ("subprocess.run" , side_effect = mocked_run ) as run_mock :
59
+ # First remove the image
60
+ subprocess .call (["docker" , "rmi" , image ])
61
+
62
+ # Now try to get the image's Command
63
+ result = uut .get_image_command (image )
64
+
65
+ # Should return a non-empty string
66
+ assert result
67
+
68
+
69
+ # -----------------------------------------------------------------------------
70
+ # get_images()
71
+
72
+
73
+ def _test_get_images (stdout : str , returncode : int = 0 ) -> Sequence [str ]:
74
+ run_mock = _mock_subprocess_run (
75
+ stdout = stdout ,
76
+ returncode = returncode ,
77
+ )
78
+ with run_mock :
44
79
return uut .get_images ()
45
80
46
81
@@ -79,18 +114,8 @@ def test_get_images__failure() -> None:
79
114
_test_get_images ("This is a pre-canned error" , 1 )
80
115
81
116
82
- def test__get_image_command__pulls_image_if_missing () -> None :
83
- """get_image_command pulls an image if missing"""
84
- image = ALT_DOCKER_IMAGE
85
-
86
- # First remove the image
87
- subprocess .call (["docker" , "rmi" , image ])
88
-
89
- # Now try to get the image's Command
90
- result = uut .get_image_command (image )
91
-
92
- # Should return a non-empty string
93
- assert result
117
+ # -----------------------------------------------------------------------------
118
+ # get_image_entrypoint()
94
119
95
120
96
121
def test_get_image_entrypoint () -> None :
@@ -105,6 +130,203 @@ def test_get_image_entrypoint__none() -> None:
105
130
assert result is None
106
131
107
132
133
+ _DOCKER_INSPECT_OUTPUT_NO_ENTRYPOINT_OLD = """
134
+ [
135
+ {
136
+ "Id": "sha256:78138d4a1048e7c080a636858484eb7170ba15e93251a4f69fc42e8c4ad288b6",
137
+ "RepoTags": [
138
+ "scuba/hello:latest"
139
+ ],
140
+ "RepoDigests": [],
141
+ "Parent": "",
142
+ "Comment": "buildkit.dockerfile.v0",
143
+ "Created": "2025-06-08T02:01:44.709546887-04:00",
144
+ "DockerVersion": "",
145
+ "Author": "",
146
+ "Config": {
147
+ "ArgsEscaped": true,
148
+ "Hostname": "",
149
+ "Domainname": "",
150
+ "User": "",
151
+ "AttachStdin": false,
152
+ "AttachStdout": false,
153
+ "AttachStderr": false,
154
+ "Tty": false,
155
+ "OpenStdin": false,
156
+ "StdinOnce": false,
157
+ "Env": [
158
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
159
+ ],
160
+ "Cmd": [
161
+ "/hello.sh"
162
+ ],
163
+ "Image": "",
164
+ "Volumes": null,
165
+ "WorkingDir": "/",
166
+ "Entrypoint": null,
167
+ "OnBuild": null,
168
+ "Labels": null
169
+ },
170
+ "Architecture": "amd64",
171
+ "Os": "linux",
172
+ "Size": 8309167,
173
+ "GraphDriver": {
174
+ "Data": {
175
+ "LowerDir": "/var/lib/docker/overlay2/i0vanzaum61dxwhffnfupowvx/diff:/var/lib/docker/overlay2/c8f9936203f4f7f53e95eacc1c1931c5a02f21f734a42d992785b938a00419ec/diff",
176
+ "MergedDir": "/var/lib/docker/overlay2/afv1jwfgnud934iv6kc4fdzsq/merged",
177
+ "UpperDir": "/var/lib/docker/overlay2/afv1jwfgnud934iv6kc4fdzsq/diff",
178
+ "WorkDir": "/var/lib/docker/overlay2/afv1jwfgnud934iv6kc4fdzsq/work"
179
+ },
180
+ "Name": "overlay2"
181
+ },
182
+ "RootFS": {
183
+ "Type": "layers",
184
+ "Layers": [
185
+ "sha256:fd2758d7a50e2b78d275ee7d1c218489f2439084449d895fa17eede6c61ab2c4",
186
+ "sha256:309ded99abb0f87ed73f4f364375a3f9765c1e04494910b07b969c0ebb09fb31",
187
+ "sha256:05b16255096123e00ba39436de05901b62d646a48d8ed514a28d843bb23393b6"
188
+ ]
189
+ },
190
+ "Metadata": {
191
+ "LastTagTime": "2025-06-08T02:01:44.76502078-04:00"
192
+ }
193
+ }
194
+ ]
195
+ """
196
+
197
+
198
+ def test_get_image_entrypoint_mocked_no_entrypoint_old () -> None :
199
+ run_mock = _mock_subprocess_run (
200
+ stdout = _DOCKER_INSPECT_OUTPUT_NO_ENTRYPOINT_OLD ,
201
+ )
202
+ with run_mock :
203
+ result = uut .get_image_entrypoint (DOCKER_IMAGE )
204
+ assert result is None
205
+
206
+
207
+ _DOCKER_INSPECT_OUTPUT_NO_ENTRYPOINT_NEW = """
208
+ [
209
+ {
210
+ "Id": "sha256:78138d4a1048e7c080a636858484eb7170ba15e93251a4f69fc42e8c4ad288b6",
211
+ "RepoTags": [
212
+ "scuba/hello:latest"
213
+ ],
214
+ "RepoDigests": [],
215
+ "Parent": "",
216
+ "Comment": "buildkit.dockerfile.v0",
217
+ "Created": "2025-06-08T02:01:44.709546887-04:00",
218
+ "DockerVersion": "",
219
+ "Author": "",
220
+ "Config": {
221
+ "Env": [
222
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
223
+ ],
224
+ "Cmd": [
225
+ "/hello.sh"
226
+ ],
227
+ "WorkingDir": "/",
228
+ "ArgsEscaped": true
229
+ },
230
+ "Architecture": "amd64",
231
+ "Os": "linux",
232
+ "Size": 8309167,
233
+ "GraphDriver": {
234
+ "Data": {
235
+ "LowerDir": "/var/lib/docker/overlay2/i0vanzaum61dxwhffnfupowvx/diff:/var/lib/docker/overlay2/c8f9936203f4f7f53e95eacc1c1931c5a02f21f734a42d992785b938a00419ec/diff",
236
+ "MergedDir": "/var/lib/docker/overlay2/afv1jwfgnud934iv6kc4fdzsq/merged",
237
+ "UpperDir": "/var/lib/docker/overlay2/afv1jwfgnud934iv6kc4fdzsq/diff",
238
+ "WorkDir": "/var/lib/docker/overlay2/afv1jwfgnud934iv6kc4fdzsq/work"
239
+ },
240
+ "Name": "overlay2"
241
+ },
242
+ "RootFS": {
243
+ "Type": "layers",
244
+ "Layers": [
245
+ "sha256:fd2758d7a50e2b78d275ee7d1c218489f2439084449d895fa17eede6c61ab2c4",
246
+ "sha256:309ded99abb0f87ed73f4f364375a3f9765c1e04494910b07b969c0ebb09fb31",
247
+ "sha256:05b16255096123e00ba39436de05901b62d646a48d8ed514a28d843bb23393b6"
248
+ ]
249
+ },
250
+ "Metadata": {
251
+ "LastTagTime": "2025-06-08T02:01:44.76502078-04:00"
252
+ }
253
+ }
254
+ ]
255
+ """
256
+
257
+
258
+ def test_get_image_entrypoint_mocked_no_entrypoint_new () -> None :
259
+ run_mock = _mock_subprocess_run (
260
+ stdout = _DOCKER_INSPECT_OUTPUT_NO_ENTRYPOINT_NEW ,
261
+ )
262
+ with run_mock :
263
+ result = uut .get_image_entrypoint (DOCKER_IMAGE )
264
+ assert result is None
265
+
266
+
267
+ _DOCKER_INSPECT_OUTPUT_ENTRYPOINT = """
268
+ [
269
+ {
270
+ "Id": "sha256:d8c0eab119d7bd0c449e62023bb045a4996dc39078da9843b2483605a15a7bb8",
271
+ "RepoTags": [
272
+ "scuba/entrypoint-test:latest"
273
+ ],
274
+ "RepoDigests": [],
275
+ "Parent": "",
276
+ "Comment": "buildkit.dockerfile.v0",
277
+ "Created": "2025-06-08T02:01:43.869621065-04:00",
278
+ "DockerVersion": "",
279
+ "Author": "",
280
+ "Config": {
281
+ "Env": [
282
+ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
283
+ ],
284
+ "Entrypoint": [
285
+ "/entrypoint.sh"
286
+ ],
287
+ "WorkingDir": "/"
288
+ },
289
+ "Architecture": "amd64",
290
+ "Os": "linux",
291
+ "Size": 8309289,
292
+ "GraphDriver": {
293
+ "Data": {
294
+ "LowerDir": "/var/lib/docker/overlay2/s8yfiyigocp8jmafjlwrnvbjh/diff:/var/lib/docker/overlay2/c8f9936203f4f7f53e95eacc1c1931c5a02f21f734a42d992785b938a00419ec/diff",
295
+ "MergedDir": "/var/lib/docker/overlay2/bko2v72yngijjzs77qw42im5c/merged",
296
+ "UpperDir": "/var/lib/docker/overlay2/bko2v72yngijjzs77qw42im5c/diff",
297
+ "WorkDir": "/var/lib/docker/overlay2/bko2v72yngijjzs77qw42im5c/work"
298
+ },
299
+ "Name": "overlay2"
300
+ },
301
+ "RootFS": {
302
+ "Type": "layers",
303
+ "Layers": [
304
+ "sha256:fd2758d7a50e2b78d275ee7d1c218489f2439084449d895fa17eede6c61ab2c4",
305
+ "sha256:b9d8112de4c9dbe254f0f9264f5bbd2b4af2ede492a0a4795b7cf899257bea60",
306
+ "sha256:89c4ad1aa7abed7e63c84d12cf81771472b64e1a6fbe9169a6c0e22e0f0aceb7"
307
+ ]
308
+ },
309
+ "Metadata": {
310
+ "LastTagTime": "2025-06-08T02:01:43.92217129-04:00"
311
+ }
312
+ }
313
+ ]
314
+ """
315
+
316
+
317
+ def test_get_image_entrypoint_mocked () -> None :
318
+ run_mock = _mock_subprocess_run (
319
+ stdout = _DOCKER_INSPECT_OUTPUT_ENTRYPOINT ,
320
+ )
321
+ with run_mock :
322
+ result = uut .get_image_entrypoint (DOCKER_IMAGE )
323
+ assert result == ["/entrypoint.sh" ]
324
+
325
+
326
+ # -----------------------------------------------------------------------------
327
+ # make_vol_opt()
328
+
329
+
108
330
def test_make_vol_opt_no_opts () -> None :
109
331
assert (
110
332
uut .make_vol_opt (Path ("/hostdir" ), Path ("/contdir" ))
0 commit comments