summaryrefslogtreecommitdiffstats
path: root/tests/test_vdkr.py
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-01-01 17:15:29 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-09 03:32:52 +0000
commit1165c61f5ab8ada644c7def03e991890c4d380ca (patch)
tree1cd1c1d58039afad5a1d24b5c10d8030237e2d7c /tests/test_vdkr.py
parentc32e1081c81ba27f0d5a21a1885601f04d329d21 (diff)
downloadmeta-virtualization-1165c61f5ab8ada644c7def03e991890c4d380ca.tar.gz
tests: add pytest framework for vdkr and vpdmn
Add pytest-based test suite for testing vdkr and vpdmn CLI tools. Tests use a separate state directory (~/.vdkr-test/) to avoid interfering with production images. Test files: - conftest.py: Pytest fixtures for VdkrRunner and VpdmnRunner - test_vdkr.py: Docker CLI tests (images, vimport, vrun, volumes, etc.) - test_vpdmn.py: Podman CLI tests (mirrors vdkr test coverage) - memres-test.sh: Helper script for running tests with memres - pytest.ini: Pytest configuration and markers Test categories: - Basic operations: images, info, version - Import/export: vimport, load, save - Container execution: vrun, run, exec - Storage management: system df, vstorage - Memory resident mode: memres/vmemres start/stop/status Running tests: pytest tests/test_vdkr.py -v --vdkr-dir /tmp/vcontainer-standalone pytest tests/test_vpdmn.py -v --vdkr-dir /tmp/vcontainer-standalone Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'tests/test_vdkr.py')
-rw-r--r--tests/test_vdkr.py726
1 files changed, 726 insertions, 0 deletions
diff --git a/tests/test_vdkr.py b/tests/test_vdkr.py
new file mode 100644
index 00000000..07715a66
--- /dev/null
+++ b/tests/test_vdkr.py
@@ -0,0 +1,726 @@
1# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
2#
3# SPDX-License-Identifier: MIT
4"""
5Tests for vdkr - Docker CLI for cross-architecture emulation.
6
7These tests verify vdkr functionality including:
8- Memory resident mode (memres)
9- Image management (images, pull, import, save, load)
10- Container execution (vrun)
11- System commands (system df, system prune)
12- Storage management (vstorage list, path, df, clean)
13
14Tests use a separate state directory (~/.vdkr-test/) to avoid
15interfering with user's images in ~/.vdkr/.
16
17Run with:
18 pytest tests/test_vdkr.py -v --vdkr-dir /tmp/vdkr-standalone
19
20Run with memres already started (faster):
21 ./tests/memres-test.sh start --vdkr-dir /tmp/vdkr-standalone
22 pytest tests/test_vdkr.py -v --vdkr-dir /tmp/vdkr-standalone --skip-destructive
23
24Run with OCI image for import tests:
25 pytest tests/test_vdkr.py -v --vdkr-dir /tmp/vdkr-standalone --oci-image /path/to/container-oci
26"""
27
28import pytest
29import json
30import os
31
32
33class TestMemresBasic:
34 """Test memory resident mode basic operations.
35
36 These tests use a separate state directory (~/.vdkr-test/) so they
37 don't interfere with user's memres in ~/.vdkr/.
38 """
39
40 def test_memres_start(self, vdkr):
41 """Test starting memory resident mode."""
42 # Stop first if running
43 vdkr.memres_stop()
44
45 result = vdkr.memres_start(timeout=180)
46 assert result.returncode == 0, f"memres start failed: {result.stderr}"
47
48 def test_memres_status(self, vdkr):
49 """Test checking memory resident status."""
50 if not vdkr.is_memres_running():
51 vdkr.memres_start(timeout=180)
52
53 result = vdkr.memres_status()
54 assert result.returncode == 0
55 assert "running" in result.stdout.lower() or "started" in result.stdout.lower()
56
57 def test_memres_stop(self, vdkr):
58 """Test stopping memory resident mode."""
59 # Ensure running first
60 if not vdkr.is_memres_running():
61 vdkr.memres_start(timeout=180)
62
63 result = vdkr.memres_stop()
64 assert result.returncode == 0
65
66 # Verify stopped
67 status = vdkr.memres_status()
68 assert status.returncode != 0 or "not running" in status.stdout.lower()
69
70 def test_memres_restart(self, vdkr):
71 """Test restarting memory resident mode."""
72 result = vdkr.run("memres", "restart", timeout=180)
73 assert result.returncode == 0
74
75 # Verify running
76 assert vdkr.is_memres_running()
77
78
79class TestImages:
80 """Test image management commands."""
81
82 def test_images_list(self, memres_session):
83 """Test images command."""
84 vdkr = memres_session
85 vdkr.ensure_memres()
86 result = vdkr.images()
87 assert result.returncode == 0
88 # Should have header line at minimum
89 assert "REPOSITORY" in result.stdout or "IMAGE" in result.stdout
90
91 @pytest.mark.network
92 def test_pull_alpine(self, memres_session):
93 """Test pulling alpine image from registry."""
94 vdkr = memres_session
95 vdkr.ensure_memres()
96
97 # Pull alpine (small image)
98 result = vdkr.pull("alpine:latest", timeout=300)
99 assert result.returncode == 0
100
101 # Verify it appears in images
102 images = vdkr.images()
103 assert "alpine" in images.stdout
104
105 def test_rmi(self, memres_session):
106 """Test removing an image."""
107 vdkr = memres_session
108
109 # Ensure we have alpine to test with
110 vdkr.ensure_alpine()
111
112 # Force remove to handle containers using the image
113 result = vdkr.run("rmi", "-f", "alpine:latest", check=False)
114 assert result.returncode == 0
115
116
117class TestVimport:
118 """Test vimport command for OCI image import."""
119
120 def test_vimport_oci(self, memres_session, oci_image):
121 """Test importing an OCI directory."""
122 if oci_image is None:
123 pytest.skip("No OCI image provided (use --oci-image)")
124
125 vdkr = memres_session
126 vdkr.ensure_memres()
127 result = vdkr.vimport(oci_image, "test-import:latest", timeout=180)
128 assert result.returncode == 0
129
130 # Verify it appears in images
131 images = vdkr.images()
132 assert "test-import" in images.stdout
133
134
135class TestSaveLoad:
136 """Test save and load commands."""
137
138 def test_save_and_load(self, memres_session, temp_dir):
139 """Test saving and loading an image."""
140 vdkr = memres_session
141
142 # Ensure we have alpine
143 vdkr.ensure_alpine()
144
145 tar_path = temp_dir / "test-save.tar"
146
147 # Save
148 result = vdkr.save(tar_path, "alpine:latest", timeout=180)
149 assert result.returncode == 0
150 assert tar_path.exists()
151 assert tar_path.stat().st_size > 0
152
153 # Remove the image
154 vdkr.run("rmi", "-f", "alpine:latest", check=False)
155
156 # Load
157 result = vdkr.load(tar_path, timeout=180)
158 assert result.returncode == 0
159
160 # Verify it's back
161 images = vdkr.images()
162 assert "alpine" in images.stdout
163
164
165class TestVrun:
166 """Test vrun command for container execution."""
167
168 def test_vrun_echo(self, memres_session):
169 """Test running echo command in a container."""
170 vdkr = memres_session
171 vdkr.ensure_alpine()
172
173 result = vdkr.vrun("alpine:latest", "/bin/echo", "hello", "world")
174 assert result.returncode == 0
175 assert "hello world" in result.stdout
176
177 def test_vrun_uname(self, memres_session, arch):
178 """Test running uname to verify architecture."""
179 vdkr = memres_session
180 vdkr.ensure_alpine()
181
182 result = vdkr.vrun("alpine:latest", "/bin/uname", "-m")
183 assert result.returncode == 0
184
185 # Check architecture matches
186 expected_arch = "x86_64" if arch == "x86_64" else "aarch64"
187 assert expected_arch in result.stdout
188
189 def test_vrun_exit_code(self, memres_session):
190 """Test container command execution."""
191 vdkr = memres_session
192 vdkr.ensure_alpine()
193
194 # Run command that exits with code 1 (false command)
195 result = vdkr.run("vrun", "alpine:latest", "/bin/false",
196 check=False, timeout=60)
197 # Container exit codes may or may not be propagated depending on vdkr implementation
198 # At minimum, verify the command ran (no crash/timeout)
199 # Note: exit code propagation is a future enhancement
200 assert result.returncode in [0, 1], f"Unexpected return code: {result.returncode}"
201
202
203class TestInspect:
204 """Test inspect command."""
205
206 def test_inspect_image(self, memres_session):
207 """Test inspecting an image."""
208 vdkr = memres_session
209 vdkr.ensure_alpine()
210
211 result = vdkr.inspect("alpine:latest")
212 assert result.returncode == 0
213
214 # Should be valid JSON
215 data = json.loads(result.stdout)
216 assert isinstance(data, list)
217 assert len(data) > 0
218
219
220class TestHistory:
221 """Test history command."""
222
223 def test_history(self, memres_session):
224 """Test showing image history."""
225 vdkr = memres_session
226 vdkr.ensure_alpine()
227
228 result = vdkr.run("history", "alpine:latest")
229 assert result.returncode == 0
230 assert "IMAGE" in result.stdout or "CREATED" in result.stdout
231
232
233class TestClean:
234 """Test clean command."""
235
236 def test_clean(self, vdkr, request):
237 """Test cleaning state directory."""
238 if request.config.getoption("--skip-destructive"):
239 pytest.skip("Skipped with --skip-destructive")
240
241 # Stop memres first
242 vdkr.memres_stop()
243
244 result = vdkr.clean()
245 assert result.returncode == 0
246
247
248class TestFallbackMode:
249 """Test fallback to regular QEMU mode when memres not running."""
250
251 @pytest.mark.slow
252 def test_images_without_memres(self, vdkr, request):
253 """Test images command works without memres (slower)."""
254 if request.config.getoption("--skip-destructive"):
255 pytest.skip("Skipped with --skip-destructive")
256
257 # Ensure memres is stopped
258 vdkr.memres_stop()
259
260 # This should still work, just slower
261 result = vdkr.images(timeout=120)
262 assert result.returncode == 0
263
264
265class TestContainerLifecycle:
266 """Test container lifecycle commands."""
267
268 @pytest.mark.slow
269 def test_run_detached_and_manage(self, memres_session):
270 """Test running a detached container and managing it."""
271 vdkr = memres_session
272 vdkr.ensure_alpine()
273
274 # Run a container in detached mode
275 # Note: vdkr run auto-prepends "docker run", so just pass the docker run args
276 result = vdkr.run("run", "-d", "--name", "test-container", "alpine:latest", "sleep", "300",
277 timeout=60, check=False)
278 if result.returncode != 0:
279 # Show error for debugging
280 print(f"Failed to start detached container: {result.stderr}")
281 pytest.skip("Could not start detached container")
282
283 try:
284 # List containers
285 ps_result = vdkr.run("ps")
286 assert "test-container" in ps_result.stdout
287
288 # Stop container
289 stop_result = vdkr.run("stop", "test-container", timeout=30)
290 assert stop_result.returncode == 0
291
292 # Remove container
293 rm_result = vdkr.run("rm", "test-container")
294 assert rm_result.returncode == 0
295
296 finally:
297 # Cleanup
298 vdkr.run("rm", "-f", "test-container", check=False)
299
300
301class TestVolumeMounts:
302 """Test volume mount functionality.
303
304 Volume mounts require memres to be running.
305 """
306
307 def test_volume_mount_read_file(self, memres_session, temp_dir):
308 """Test mounting a host directory and reading a file from it."""
309 vdkr = memres_session
310 vdkr.ensure_alpine()
311
312 # Create a test file on host
313 test_file = temp_dir / "testfile.txt"
314 test_content = "Hello from host volume!"
315 test_file.write_text(test_content)
316
317 # Run container with volume mount and read the file
318 result = vdkr.run("vrun", "-v", f"{temp_dir}:/data", "alpine:latest",
319 "cat", "/data/testfile.txt", timeout=60)
320 assert result.returncode == 0
321 assert test_content in result.stdout
322
323 def test_volume_mount_write_file(self, memres_session, temp_dir):
324 """Test writing a file in a mounted volume."""
325 vdkr = memres_session
326 vdkr.ensure_alpine()
327
328 # Create a script that writes to a file - avoids shell metacharacter issues
329 # when passing through multiple shells (host -> vdkr -> runner -> guest -> container)
330 # Include sync to ensure write is flushed to host via 9p/virtio-fs
331 script = temp_dir / "write.sh"
332 script.write_text("#!/bin/sh\necho 'Created in container' > /data/output.txt\nsync\n")
333 script.chmod(0o755)
334
335 # Run the script inside the container
336 result = vdkr.run("vrun", "-v", f"{temp_dir}:/data", "alpine:latest",
337 "/data/write.sh", timeout=60)
338 assert result.returncode == 0
339
340 # Verify the file was synced back to host
341 output_file = temp_dir / "output.txt"
342 assert output_file.exists(), "Output file should be synced back to host"
343 assert "Created in container" in output_file.read_text()
344
345 def test_volume_mount_read_only(self, memres_session, temp_dir):
346 """Test read-only volume mount."""
347 vdkr = memres_session
348 vdkr.ensure_alpine()
349
350 # Create a test file
351 test_file = temp_dir / "readonly.txt"
352 test_file.write_text("Read-only content")
353
354 # Can read from ro mount
355 result = vdkr.run("vrun", "-v", f"{temp_dir}:/data:ro", "alpine:latest",
356 "cat", "/data/readonly.txt", timeout=60)
357 assert result.returncode == 0
358 assert "Read-only content" in result.stdout
359
360 def test_volume_mount_multiple(self, memres_session, temp_dir):
361 """Test multiple volume mounts."""
362 vdkr = memres_session
363 vdkr.ensure_alpine()
364
365 # Create two directories with test files
366 dir1 = temp_dir / "dir1"
367 dir2 = temp_dir / "dir2"
368 dir1.mkdir()
369 dir2.mkdir()
370
371 (dir1 / "file1.txt").write_text("Content from dir1")
372 (dir2 / "file2.txt").write_text("Content from dir2")
373
374 # Create a script to avoid shell metacharacter issues with ';' or '&&'
375 script = temp_dir / "read_both.sh"
376 script.write_text("#!/bin/sh\ncat /data1/file1.txt\ncat /data2/file2.txt\n")
377 script.chmod(0o755)
378
379 # Mount both directories plus the script
380 result = vdkr.run("vrun",
381 "-v", f"{temp_dir}:/scripts",
382 "-v", f"{dir1}:/data1",
383 "-v", f"{dir2}:/data2",
384 "alpine:latest",
385 "/scripts/read_both.sh",
386 timeout=60)
387 assert result.returncode == 0
388 assert "Content from dir1" in result.stdout
389 assert "Content from dir2" in result.stdout
390
391 def test_volume_mount_with_run_command(self, memres_session, temp_dir):
392 """Test volume mount with run command (not vrun)."""
393 vdkr = memres_session
394 vdkr.ensure_alpine()
395
396 # Create a test file
397 test_file = temp_dir / "runtest.txt"
398 test_file.write_text("Testing run command volumes")
399
400 # Use run command with volume
401 result = vdkr.run("run", "--rm", "-v", f"{temp_dir}:/data",
402 "alpine:latest", "cat", "/data/runtest.txt",
403 timeout=60)
404 assert result.returncode == 0
405 assert "Testing run command volumes" in result.stdout
406
407 def test_volume_mount_requires_memres(self, vdkr, temp_dir, request):
408 """Test that volume mounts fail gracefully without memres."""
409 if request.config.getoption("--skip-destructive"):
410 pytest.skip("Skipped with --skip-destructive")
411
412 # Ensure memres is stopped
413 vdkr.memres_stop()
414
415 # Create a test file
416 test_file = temp_dir / "test.txt"
417 test_file.write_text("test")
418
419 # Try to use volume mount without memres - should fail with clear message
420 result = vdkr.run("vrun", "-v", f"{temp_dir}:/data", "alpine:latest",
421 "cat", "/data/test.txt", check=False, timeout=30)
422
423 # Should fail because memres is not running
424 assert result.returncode != 0
425 assert "memres" in result.stderr.lower() or "daemon" in result.stderr.lower()
426
427
428class TestSystem:
429 """Test system commands (run inside VM)."""
430
431 def test_system_df(self, memres_session):
432 """Test system df command."""
433 vdkr = memres_session
434 vdkr.ensure_memres()
435
436 result = vdkr.run("system", "df")
437 assert result.returncode == 0
438 # Should show images, containers, volumes headers
439 assert "IMAGES" in result.stdout.upper() or "TYPE" in result.stdout.upper()
440
441 def test_system_df_verbose(self, memres_session):
442 """Test system df -v command."""
443 vdkr = memres_session
444 vdkr.ensure_memres()
445
446 result = vdkr.run("system", "df", "-v")
447 assert result.returncode == 0
448 # Verbose mode shows more details
449 assert "IMAGES" in result.stdout.upper() or "TYPE" in result.stdout.upper()
450
451 def test_system_prune_dry_run(self, memres_session):
452 """Test system prune with dry run (doesn't actually delete)."""
453 vdkr = memres_session
454 vdkr.ensure_memres()
455
456 # Just verify the command runs (don't actually prune in tests)
457 # Add -f to skip confirmation prompt
458 result = vdkr.run("system", "prune", "-f", check=False)
459 # Command may return 0 even with nothing to prune
460 assert result.returncode == 0
461
462 def test_system_without_subcommand(self, memres_session):
463 """Test system command without subcommand shows error."""
464 vdkr = memres_session
465 vdkr.ensure_memres()
466
467 result = vdkr.run("system", check=False)
468 assert result.returncode != 0
469 assert "subcommand" in result.stderr.lower() or "requires" in result.stderr.lower()
470
471
472class TestVstorage:
473 """Test vstorage commands (host-side storage management).
474
475 These commands run on the host and don't require memres.
476 """
477
478 def test_vstorage_list(self, vdkr):
479 """Test vstorage list command."""
480 # Ensure there's something to list by starting memres briefly
481 vdkr.ensure_memres()
482
483 result = vdkr.run("vstorage", "list", check=False)
484 # vstorage list is an alias for vstorage
485 assert result.returncode == 0
486 assert "storage" in result.stdout.lower() or "path" in result.stdout.lower()
487
488 def test_vstorage_default(self, vdkr):
489 """Test vstorage with no subcommand (defaults to list)."""
490 vdkr.ensure_memres()
491
492 result = vdkr.run("vstorage", check=False)
493 assert result.returncode == 0
494 # Should show storage info
495 assert "storage" in result.stdout.lower() or "vdkr" in result.stdout.lower()
496
497 def test_vstorage_path(self, vdkr, arch):
498 """Test vstorage path command."""
499 result = vdkr.run("vstorage", "path", check=False)
500 assert result.returncode == 0
501 # Output should contain the architecture or .vdkr path
502 assert arch in result.stdout or ".vdkr" in result.stdout
503
504 def test_vstorage_path_specific_arch(self, vdkr):
505 """Test vstorage path with specific architecture."""
506 # Use the same arch as the runner to avoid cross-arch issues
507 arch = vdkr.arch
508 result = vdkr.run("vstorage", "path", arch, check=False)
509 assert result.returncode == 0
510 assert arch in result.stdout
511
512 def test_vstorage_df(self, vdkr):
513 """Test vstorage df command."""
514 # Ensure there's something to show
515 vdkr.ensure_memres()
516
517 result = vdkr.run("vstorage", "df", check=False)
518 assert result.returncode == 0
519 # Should show size information (may be empty if no state yet)
520
521 def test_vstorage_shows_memres_status(self, vdkr):
522 """Test that vstorage list shows memres running status."""
523 vdkr.ensure_memres()
524
525 result = vdkr.run("vstorage", "list", check=False)
526 assert result.returncode == 0
527 # Should show running status when memres is active
528 assert "running" in result.stdout.lower() or "memres" in result.stdout.lower() \
529 or "status" in result.stdout.lower()
530
531 def test_vstorage_clean_current_arch(self, vdkr, request):
532 """Test vstorage clean for current architecture."""
533 if request.config.getoption("--skip-destructive"):
534 pytest.skip("Skipped with --skip-destructive")
535
536 # Ensure there's something to clean
537 vdkr.ensure_memres()
538 vdkr.memres_stop()
539
540 result = vdkr.run("vstorage", "clean", check=False)
541 assert result.returncode == 0
542 assert "clean" in result.stdout.lower()
543
544 def test_vstorage_unknown_subcommand(self, vdkr):
545 """Test vstorage with unknown subcommand shows error."""
546 result = vdkr.run("vstorage", "invalid", check=False)
547 assert result.returncode != 0
548 assert "unknown" in result.stderr.lower() or "usage" in result.stderr.lower()
549
550
551class TestRun:
552 """Test run command with docker run options."""
553
554 def test_run_with_entrypoint(self, memres_session):
555 """Test run command with --entrypoint override."""
556 vdkr = memres_session
557 vdkr.ensure_alpine()
558
559 result = vdkr.run("run", "--rm", "--entrypoint", "/bin/echo",
560 "alpine:latest", "hello", "from", "entrypoint")
561 assert result.returncode == 0
562 assert "hello from entrypoint" in result.stdout
563
564 def test_run_with_env_var(self, memres_session):
565 """Test run command with environment variable."""
566 vdkr = memres_session
567 vdkr.ensure_alpine()
568
569 # Use printenv instead of echo $MY_VAR to avoid shell quoting issues
570 result = vdkr.run("run", "--rm", "-e", "MY_VAR=test_value",
571 "alpine:latest", "printenv", "MY_VAR")
572 assert result.returncode == 0
573 assert "test_value" in result.stdout
574
575
576class TestRemoteFetchAndCrossInstall:
577 """Test remote container fetch and cross-install workflow.
578
579 These tests verify the full workflow for bundling containers into images:
580 1. Pull container from remote registry
581 2. Verify container is functional
582 3. Export container storage (simulates cross-install bundle)
583 4. Import storage into fresh state (simulates target boot)
584 5. Verify container works after import
585
586 Requires network access - use @pytest.mark.network marker.
587 """
588
589 @pytest.mark.network
590 def test_pull_busybox(self, memres_session):
591 """Test pulling busybox image from registry."""
592 vdkr = memres_session
593 vdkr.ensure_memres()
594
595 # Pull busybox (very small image, faster than alpine for this test)
596 result = vdkr.pull("busybox:latest", timeout=300)
597 assert result.returncode == 0
598
599 # Verify it appears in images
600 images = vdkr.images()
601 assert "busybox" in images.stdout
602
603 @pytest.mark.network
604 def test_pull_and_run(self, memres_session):
605 """Test that pulled container can be executed."""
606 vdkr = memres_session
607 vdkr.ensure_memres()
608
609 # Ensure we have busybox
610 images = vdkr.images()
611 if "busybox" not in images.stdout:
612 vdkr.pull("busybox:latest", timeout=300)
613
614 # Run a command in the pulled container
615 result = vdkr.vrun("busybox:latest", "/bin/echo", "remote_fetch_works")
616 assert result.returncode == 0
617 assert "remote_fetch_works" in result.stdout
618
619 @pytest.mark.network
620 def test_cross_install_workflow(self, memres_session, temp_dir):
621 """Test full cross-install workflow: pull -> export -> import -> run.
622
623 This simulates:
624 1. Build host: pull container from registry
625 2. Build host: export Docker storage to tar (for bundling into image)
626 3. Target boot: import storage tar
627 4. Target: run the container
628
629 This is the core workflow for container-cross-install.
630 """
631 vdkr = memres_session
632 vdkr.ensure_memres()
633
634 # Step 1: Pull container from remote registry
635 images = vdkr.images()
636 if "busybox" not in images.stdout:
637 result = vdkr.pull("busybox:latest", timeout=300)
638 assert result.returncode == 0
639
640 # Step 2: Save container to tar (simulates bundle export)
641 bundle_tar = temp_dir / "cross-install-bundle.tar"
642 result = vdkr.save(bundle_tar, "busybox:latest", timeout=180)
643 assert result.returncode == 0
644 assert bundle_tar.exists()
645 assert bundle_tar.stat().st_size > 0
646
647 # Step 3: Remove original image (simulates fresh target state)
648 vdkr.run("rmi", "-f", "busybox:latest", check=False)
649 images = vdkr.images()
650 # Verify removed (may still show if other tags exist)
651
652 # Step 4: Load from bundle tar (simulates target importing bundled storage)
653 result = vdkr.load(bundle_tar, timeout=180)
654 assert result.returncode == 0
655
656 # Step 5: Verify container works after import
657 images = vdkr.images()
658 assert "busybox" in images.stdout
659
660 result = vdkr.vrun("busybox:latest", "/bin/echo", "cross_install_success")
661 assert result.returncode == 0
662 assert "cross_install_success" in result.stdout
663
664 @pytest.mark.network
665 def test_pull_verify_architecture(self, memres_session, arch):
666 """Test that pulled container matches target architecture."""
667 vdkr = memres_session
668 vdkr.ensure_memres()
669
670 # Ensure we have busybox
671 images = vdkr.images()
672 if "busybox" not in images.stdout:
673 vdkr.pull("busybox:latest", timeout=300)
674
675 # Run uname to verify architecture inside container
676 result = vdkr.vrun("busybox:latest", "/bin/uname", "-m")
677 assert result.returncode == 0
678
679 # Check architecture matches target
680 expected_arch = "x86_64" if arch == "x86_64" else "aarch64"
681 assert expected_arch in result.stdout, \
682 f"Architecture mismatch: expected {expected_arch}, got {result.stdout.strip()}"
683
684 @pytest.mark.network
685 def test_multiple_containers_bundle(self, memres_session, temp_dir):
686 """Test bundling multiple containers (simulates multi-container image)."""
687 vdkr = memres_session
688 vdkr.ensure_memres()
689
690 containers = ["busybox:latest", "alpine:latest"]
691 bundle_tars = []
692
693 # Pull and save each container
694 for container in containers:
695 name = container.split(":")[0]
696 images = vdkr.images()
697 if name not in images.stdout:
698 result = vdkr.pull(container, timeout=300)
699 assert result.returncode == 0
700
701 tar_path = temp_dir / f"{name}-bundle.tar"
702 result = vdkr.save(tar_path, container, timeout=180)
703 assert result.returncode == 0
704 bundle_tars.append((container, tar_path))
705
706 # Remove all containers
707 for container, _ in bundle_tars:
708 vdkr.run("rmi", "-f", container, check=False)
709
710 # Load all bundles (simulates target with multiple bundled containers)
711 for container, tar_path in bundle_tars:
712 result = vdkr.load(tar_path, timeout=180)
713 assert result.returncode == 0
714
715 # Verify all containers work
716 images = vdkr.images()
717 for container, _ in bundle_tars:
718 name = container.split(":")[0]
719 assert name in images.stdout, f"{name} not found after load"
720
721 # Run a command in each
722 result = vdkr.vrun("busybox:latest", "/bin/echo", "busybox_ok")
723 assert "busybox_ok" in result.stdout
724
725 result = vdkr.vrun("alpine:latest", "/bin/echo", "alpine_ok")
726 assert "alpine_ok" in result.stdout