diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-01-08 04:56:39 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-01-21 18:00:26 -0500 |
| commit | c55674f76684a18eb6ddc714c4e91440600aea1a (patch) | |
| tree | 6283722847d95aa4fd922abdfc9fce2195853e92 /tests | |
| parent | 63b5365a7c66459bf2f79613bcd380b67055d011 (diff) | |
| download | meta-virtualization-c55674f76684a18eb6ddc714c4e91440600aea1a.tar.gz | |
tests: add tests for auto-start and dynamic port forwarding
Add test coverage for new vmemres features:
TestAutoStartDaemon:
- test_auto_start_on_first_command: Verify daemon auto-starts
- test_no_daemon_flag: Verify --no-daemon uses ephemeral mode
- test_vconfig_auto_daemon: Test auto-daemon config setting
- test_vconfig_idle_timeout: Test idle-timeout config setting
TestDynamicPortForwarding:
- test_dynamic_port_forward_run: Run -d -p adds forward dynamically
- test_port_forward_cleanup_on_stop: Forwards removed on stop
- test_port_forward_cleanup_on_rm: Forwards removed on rm
- test_multiple_dynamic_port_forwards: Multiple containers work
TestPortForwardRegistry:
- test_port_forward_cleared_on_memres_stop: Registry cleared
Also add ensure_busybox() helper to both VdkrRunner and VpdmnRunner.
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/conftest.py | 14 | ||||
| -rw-r--r-- | tests/test_vdkr.py | 236 |
2 files changed, 250 insertions, 0 deletions
diff --git a/tests/conftest.py b/tests/conftest.py index b35f190c..71b86430 100644 --- a/tests/conftest.py +++ b/tests/conftest.py | |||
| @@ -400,6 +400,13 @@ class VdkrRunner: | |||
| 400 | if not self.has_image("alpine"): | 400 | if not self.has_image("alpine"): |
| 401 | self.pull("alpine:latest", timeout=timeout) | 401 | self.pull("alpine:latest", timeout=timeout) |
| 402 | 402 | ||
| 403 | def ensure_busybox(self, timeout=300): | ||
| 404 | """Ensure busybox:latest is available, pulling if necessary.""" | ||
| 405 | # Ensure memres is running first (in case a previous test stopped it) | ||
| 406 | self.ensure_memres() | ||
| 407 | if not self.has_image("busybox"): | ||
| 408 | self.pull("busybox:latest", timeout=timeout) | ||
| 409 | |||
| 403 | 410 | ||
| 404 | @pytest.fixture(scope="session") | 411 | @pytest.fixture(scope="session") |
| 405 | def vdkr(vdkr_bin, vdkr_env, arch, test_state_dir): | 412 | def vdkr(vdkr_bin, vdkr_env, arch, test_state_dir): |
| @@ -611,6 +618,13 @@ class VpdmnRunner: | |||
| 611 | if not self.has_image("alpine"): | 618 | if not self.has_image("alpine"): |
| 612 | self.pull("alpine:latest", timeout=timeout) | 619 | self.pull("alpine:latest", timeout=timeout) |
| 613 | 620 | ||
| 621 | def ensure_busybox(self, timeout=300): | ||
| 622 | """Ensure busybox:latest is available, pulling if necessary.""" | ||
| 623 | # Ensure memres is running first (in case a previous test stopped it) | ||
| 624 | self.ensure_memres() | ||
| 625 | if not self.has_image("busybox"): | ||
| 626 | self.pull("busybox:latest", timeout=timeout) | ||
| 627 | |||
| 614 | 628 | ||
| 615 | @pytest.fixture(scope="session") | 629 | @pytest.fixture(scope="session") |
| 616 | def vpdmn(vpdmn_bin, vdkr_env, arch, vpdmn_test_state_dir): | 630 | def vpdmn(vpdmn_bin, vdkr_env, arch, vpdmn_test_state_dir): |
diff --git a/tests/test_vdkr.py b/tests/test_vdkr.py index d7701057..81715d16 100644 --- a/tests/test_vdkr.py +++ b/tests/test_vdkr.py | |||
| @@ -784,3 +784,239 @@ class TestRemoteFetchAndCrossInstall: | |||
| 784 | 784 | ||
| 785 | result = vdkr.vrun("alpine:latest", "/bin/echo", "alpine_ok") | 785 | result = vdkr.vrun("alpine:latest", "/bin/echo", "alpine_ok") |
| 786 | assert "alpine_ok" in result.stdout | 786 | assert "alpine_ok" in result.stdout |
| 787 | |||
| 788 | |||
| 789 | class TestAutoStartDaemon: | ||
| 790 | """Test auto-start daemon behavior. | ||
| 791 | |||
| 792 | When auto-daemon is enabled (default), vmemres starts automatically | ||
| 793 | on the first command and stops after idle timeout. | ||
| 794 | """ | ||
| 795 | |||
| 796 | def test_auto_start_on_first_command(self, vdkr): | ||
| 797 | """Test that daemon auto-starts on first command.""" | ||
| 798 | # Stop daemon if running | ||
| 799 | vdkr.memres_stop() | ||
| 800 | assert not vdkr.is_memres_running(), "Daemon should be stopped" | ||
| 801 | |||
| 802 | # Run a command - daemon should auto-start | ||
| 803 | result = vdkr.images(timeout=180) | ||
| 804 | assert result.returncode == 0 | ||
| 805 | |||
| 806 | # Verify daemon is now running | ||
| 807 | assert vdkr.is_memres_running(), "Daemon should have auto-started" | ||
| 808 | |||
| 809 | def test_no_daemon_flag(self, vdkr): | ||
| 810 | """Test --no-daemon runs without starting daemon.""" | ||
| 811 | # Stop daemon if running | ||
| 812 | vdkr.memres_stop() | ||
| 813 | assert not vdkr.is_memres_running(), "Daemon should be stopped" | ||
| 814 | |||
| 815 | # Run with --no-daemon - should use ephemeral mode | ||
| 816 | result = vdkr.run("--no-daemon", "images", timeout=180) | ||
| 817 | assert result.returncode == 0 | ||
| 818 | |||
| 819 | # Daemon should NOT be running | ||
| 820 | assert not vdkr.is_memres_running(), "Daemon should not have started with --no-daemon" | ||
| 821 | |||
| 822 | def test_vconfig_auto_daemon(self, vdkr): | ||
| 823 | """Test vconfig auto-daemon setting.""" | ||
| 824 | # Check current value | ||
| 825 | result = vdkr.run("vconfig", "auto-daemon") | ||
| 826 | assert result.returncode == 0 | ||
| 827 | assert "true" in result.stdout.lower() or "auto-daemon" in result.stdout | ||
| 828 | |||
| 829 | # Test setting to false | ||
| 830 | result = vdkr.run("vconfig", "auto-daemon", "false") | ||
| 831 | assert result.returncode == 0 | ||
| 832 | |||
| 833 | # Reset to default | ||
| 834 | result = vdkr.run("vconfig", "auto-daemon", "--reset") | ||
| 835 | assert result.returncode == 0 | ||
| 836 | |||
| 837 | def test_vconfig_idle_timeout(self, vdkr): | ||
| 838 | """Test vconfig idle-timeout setting.""" | ||
| 839 | # Check current value | ||
| 840 | result = vdkr.run("vconfig", "idle-timeout") | ||
| 841 | assert result.returncode == 0 | ||
| 842 | |||
| 843 | # Test setting value | ||
| 844 | result = vdkr.run("vconfig", "idle-timeout", "3600") | ||
| 845 | assert result.returncode == 0 | ||
| 846 | |||
| 847 | # Reset to default | ||
| 848 | result = vdkr.run("vconfig", "idle-timeout", "--reset") | ||
| 849 | assert result.returncode == 0 | ||
| 850 | |||
| 851 | |||
| 852 | class TestDynamicPortForwarding: | ||
| 853 | """Test dynamic port forwarding via QMP. | ||
| 854 | |||
| 855 | Port forwards can be added dynamically when running detached containers, | ||
| 856 | without needing to specify them at vmemres start time. | ||
| 857 | """ | ||
| 858 | |||
| 859 | @pytest.mark.network | ||
| 860 | @pytest.mark.slow | ||
| 861 | def test_dynamic_port_forward_run(self, vdkr): | ||
| 862 | """Test that run -d -p adds port forward dynamically.""" | ||
| 863 | import subprocess | ||
| 864 | import time | ||
| 865 | |||
| 866 | # Ensure memres is running (without static port forwards) | ||
| 867 | vdkr.memres_stop() | ||
| 868 | vdkr.memres_start(timeout=180) | ||
| 869 | assert vdkr.is_memres_running() | ||
| 870 | |||
| 871 | try: | ||
| 872 | # Pull nginx:alpine if not present | ||
| 873 | vdkr.run("pull", "nginx:alpine", timeout=300) | ||
| 874 | |||
| 875 | # Run with dynamic port forward | ||
| 876 | result = vdkr.run("run", "-d", "--name", "nginx-test", "-p", "8888:80", | ||
| 877 | "nginx:alpine", timeout=60) | ||
| 878 | assert result.returncode == 0, f"nginx run failed: {result.stderr}" | ||
| 879 | |||
| 880 | # Give nginx time to start | ||
| 881 | time.sleep(3) | ||
| 882 | |||
| 883 | # Test access from host | ||
| 884 | curl_result = subprocess.run( | ||
| 885 | ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", | ||
| 886 | "http://localhost:8888"], | ||
| 887 | capture_output=True, | ||
| 888 | text=True, | ||
| 889 | timeout=10 | ||
| 890 | ) | ||
| 891 | assert curl_result.stdout == "200", \ | ||
| 892 | f"Expected HTTP 200, got {curl_result.stdout}" | ||
| 893 | |||
| 894 | # Check ps shows port forwards | ||
| 895 | ps_result = vdkr.run("ps") | ||
| 896 | assert ps_result.returncode == 0 | ||
| 897 | assert "8888" in ps_result.stdout or "Port Forwards" in ps_result.stdout | ||
| 898 | |||
| 899 | finally: | ||
| 900 | # Clean up | ||
| 901 | vdkr.run("stop", "nginx-test", timeout=10, check=False) | ||
| 902 | vdkr.run("rm", "-f", "nginx-test", check=False) | ||
| 903 | |||
| 904 | def test_port_forward_cleanup_on_stop(self, memres_session): | ||
| 905 | """Test that port forwards are cleaned up when container stops.""" | ||
| 906 | vdkr = memres_session | ||
| 907 | vdkr.ensure_memres() | ||
| 908 | |||
| 909 | # Ensure we have busybox | ||
| 910 | vdkr.ensure_busybox() | ||
| 911 | |||
| 912 | # Run a container with port forward | ||
| 913 | result = vdkr.run("run", "-d", "--name", "port-test", "-p", "9999:80", | ||
| 914 | "busybox:latest", "sleep", "300", timeout=60, check=False) | ||
| 915 | |||
| 916 | if result.returncode == 0: | ||
| 917 | # Stop the container | ||
| 918 | vdkr.run("stop", "port-test", timeout=10, check=False) | ||
| 919 | |||
| 920 | # Check ps - port forward should be removed | ||
| 921 | ps_result = vdkr.run("ps") | ||
| 922 | assert "9999" not in ps_result.stdout or "port-test" not in ps_result.stdout | ||
| 923 | |||
| 924 | # Clean up | ||
| 925 | vdkr.run("rm", "-f", "port-test", check=False) | ||
| 926 | |||
| 927 | def test_port_forward_cleanup_on_rm(self, memres_session): | ||
| 928 | """Test that port forwards are cleaned up when container is removed.""" | ||
| 929 | vdkr = memres_session | ||
| 930 | vdkr.ensure_memres() | ||
| 931 | |||
| 932 | # Ensure we have busybox | ||
| 933 | vdkr.ensure_busybox() | ||
| 934 | |||
| 935 | # Run a container with port forward | ||
| 936 | result = vdkr.run("run", "-d", "--name", "rm-test", "-p", "7777:80", | ||
| 937 | "busybox:latest", "sleep", "300", timeout=60, check=False) | ||
| 938 | |||
| 939 | if result.returncode == 0: | ||
| 940 | # Force remove the container | ||
| 941 | vdkr.run("rm", "-f", "rm-test", timeout=10, check=False) | ||
| 942 | |||
| 943 | # Check ps - port forward should be removed | ||
| 944 | ps_result = vdkr.run("ps") | ||
| 945 | assert "7777" not in ps_result.stdout or "rm-test" not in ps_result.stdout | ||
| 946 | |||
| 947 | @pytest.mark.network | ||
| 948 | @pytest.mark.slow | ||
| 949 | def test_multiple_dynamic_port_forwards(self, vdkr): | ||
| 950 | """Test multiple containers with different dynamic port forwards.""" | ||
| 951 | import time | ||
| 952 | |||
| 953 | vdkr.memres_stop() | ||
| 954 | vdkr.memres_start(timeout=180) | ||
| 955 | assert vdkr.is_memres_running() | ||
| 956 | |||
| 957 | try: | ||
| 958 | # Pull busybox | ||
| 959 | vdkr.run("pull", "busybox:latest", timeout=300, check=False) | ||
| 960 | |||
| 961 | # Run first container with port forward | ||
| 962 | result1 = vdkr.run("run", "-d", "--name", "http1", "-p", "8001:80", | ||
| 963 | "busybox:latest", "httpd", "-f", "-p", "80", | ||
| 964 | timeout=60, check=False) | ||
| 965 | |||
| 966 | # Run second container with different port forward | ||
| 967 | result2 = vdkr.run("run", "-d", "--name", "http2", "-p", "8002:80", | ||
| 968 | "busybox:latest", "httpd", "-f", "-p", "80", | ||
| 969 | timeout=60, check=False) | ||
| 970 | |||
| 971 | time.sleep(2) | ||
| 972 | |||
| 973 | # Check ps shows both port forwards | ||
| 974 | ps_result = vdkr.run("ps") | ||
| 975 | # Note: May show in port forwards section, not PORTS column | ||
| 976 | assert ps_result.returncode == 0 | ||
| 977 | |||
| 978 | # Stop first - second should still work | ||
| 979 | vdkr.run("stop", "http1", timeout=10, check=False) | ||
| 980 | |||
| 981 | # Check ps - only second port forward should remain | ||
| 982 | ps_result = vdkr.run("ps") | ||
| 983 | # http1's port should be cleaned up, http2 should remain | ||
| 984 | |||
| 985 | finally: | ||
| 986 | vdkr.run("stop", "http1", timeout=10, check=False) | ||
| 987 | vdkr.run("stop", "http2", timeout=10, check=False) | ||
| 988 | vdkr.run("rm", "-f", "http1", check=False) | ||
| 989 | vdkr.run("rm", "-f", "http2", check=False) | ||
| 990 | |||
| 991 | |||
| 992 | class TestPortForwardRegistry: | ||
| 993 | """Test port forward registry cleanup.""" | ||
| 994 | |||
| 995 | def test_port_forward_cleared_on_memres_stop(self, vdkr): | ||
| 996 | """Test that port forward registry is cleared when memres stops.""" | ||
| 997 | import os | ||
| 998 | |||
| 999 | # Start memres | ||
| 1000 | vdkr.memres_stop() | ||
| 1001 | vdkr.memres_start(timeout=180) | ||
| 1002 | assert vdkr.is_memres_running() | ||
| 1003 | |||
| 1004 | # Get state dir path | ||
| 1005 | result = vdkr.run("vstorage", "path") | ||
| 1006 | if result.returncode == 0: | ||
| 1007 | state_dir = result.stdout.strip() | ||
| 1008 | pf_file = os.path.join(state_dir, "port-forwards.txt") | ||
| 1009 | |||
| 1010 | # Run a container with port forward to create registry entry | ||
| 1011 | vdkr.ensure_busybox() | ||
| 1012 | vdkr.run("run", "-d", "--name", "pf-test", "-p", "6666:80", | ||
| 1013 | "busybox:latest", "sleep", "60", timeout=60, check=False) | ||
| 1014 | |||
| 1015 | # Stop memres - should clear port forward file | ||
| 1016 | vdkr.memres_stop() | ||
| 1017 | |||
| 1018 | # Port forward file should not exist or be empty | ||
| 1019 | if os.path.exists(pf_file): | ||
| 1020 | with open(pf_file, 'r') as f: | ||
| 1021 | content = f.read() | ||
| 1022 | assert content.strip() == "", "Port forward file should be empty" | ||
