827
VerifyContainerIsNotListed(WslcContainerName);
828
}
829
830
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Cpus)
831
+ {
832
+ auto result = RunWslc(std::format(L"container create --name {} --cpus 0.5 {} true", WslcContainerName, DebianImage.NameAndTag()));
833
+ result.Verify({.Stderr = L"", .ExitCode = 0});
834
+
835
+ const auto inspect = InspectContainer(WslcContainerName);
836
+ VERIFY_ARE_EQUAL(static_cast<int64_t>(500'000'000), inspect.HostConfig.NanoCpus);
837
+ }
838
+
839
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Cpus_Invalid)
840
+ {
841
+ auto result = RunWslc(std::format(L"container create --cpus 0 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
842
+ result.Verify({.Stderr = L"Invalid cpus argument value: '0'. Expected a positive number of CPUs (e.g. 0.5, 1, 2)\r\n", .ExitCode = 1});
843
+ EnsureContainerDoesNotExist(WslcContainerName);
844
+ }
845
+
846
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Memory)
847
+ {
848
+ auto result =
849
+ RunWslc(std::format(L"container create --name {} --memory 32M {} true", WslcContainerName, DebianImage.NameAndTag()));
850
+ // stderr not asserted: some kernels emit a swap-limit warning when --memory is set.
851
+ result.Verify({.ExitCode = 0});
852
+
853
+ const auto inspect = InspectContainer(WslcContainerName);
854
+ VERIFY_ARE_EQUAL(static_cast<int64_t>(32) * 1024 * 1024, inspect.HostConfig.Memory);
855
+ }
856
+
857
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Memory_Invalid)
858
+ {
859
+ auto result =
860
+ RunWslc(std::format(L"container create --memory invalid --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
861
+ result.Verify({.Stderr = L"Invalid memory argument value: 'invalid'. Expected a memory size (e.g. 256M, 1G)\r\n", .ExitCode = 1});
862
+ EnsureContainerDoesNotExist(WslcContainerName);
863
+ }
864
+
865
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Ulimit)
866
+ {
867
+ auto result = RunWslc(std::format(
868
+ L"container create --name {} --ulimit nofile=1024:2048 --ulimit nproc=512 {} true", WslcContainerName, DebianImage.NameAndTag()));
869
+ result.Verify({.Stderr = L"", .ExitCode = 0});
870
+
871
+ const auto inspect = InspectContainer(WslcContainerName);
872
+ VERIFY_ARE_EQUAL(static_cast<size_t>(2), inspect.HostConfig.Ulimits.size());
873
+
874
+ std::map<std::string, std::pair<int64_t, int64_t>> byName;
875
+ for (const auto& ul : inspect.HostConfig.Ulimits)
876
+ {
877
+ byName[ul.Name] = {ul.Soft, ul.Hard};
878
+ }
879
+
880
+ VERIFY_IS_TRUE(byName.contains("nofile"));
881
+ VERIFY_ARE_EQUAL(static_cast<int64_t>(1024), byName["nofile"].first);
882
+ VERIFY_ARE_EQUAL(static_cast<int64_t>(2048), byName["nofile"].second);
883
+
884
+ VERIFY_IS_TRUE(byName.contains("nproc"));
885
+ VERIFY_ARE_EQUAL(static_cast<int64_t>(512), byName["nproc"].first);
886
+ VERIFY_ARE_EQUAL(static_cast<int64_t>(512), byName["nproc"].second);
887
+ }
888
+
889
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Ulimit_Invalid)
890
+ {
891
+ auto result = RunWslc(std::format(L"container create --ulimit nofile --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
892
+ result.Verify(
893
+ {.Stderr = L"Invalid ulimit argument value: 'nofile'. Expected <name>=<soft>[:<hard>] (use -1 for unlimited)\r\n", .ExitCode = 1});
894
+ EnsureContainerDoesNotExist(WslcContainerName);
895
+ }
896
+
897
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Entrypoint)
898
+ {
899
+ auto result =
900
+ RunWslc(std::format(L"container create --name {} --entrypoint /bin/whoami {}", WslcContainerName, DebianImage.NameAndTag()));
901
+ result.Verify({.Stderr = L"", .ExitCode = 0});
902
+
903
+ result = RunWslc(std::format(L"container start -a {}", WslcContainerName));
904
+ result.Verify({.Stdout = L"root\n", .Stderr = L"", .ExitCode = 0});
905
+ }
906
+
907
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_EnvFile)
908
+ {
909
+ WriteTestFile(
910
+ EnvTestFile1, {"WSLC_TEST_CREATE_ENV_FILE_A=create-env-file-a", "WSLC_TEST_CREATE_ENV_FILE_B=create-env-file-b"});
911
+
912
+ auto result = RunWslc(std::format(
913
+ L"container create --name {} --env-file {} {} env", WslcContainerName, EscapePath(EnvTestFile1.wstring()), DebianImage.NameAndTag()));
914
+ result.Verify({.Stderr = L"", .ExitCode = 0});
915
+
916
+ result = RunWslc(std::format(L"container start -a {}", WslcContainerName));
917
+ result.Verify({.Stderr = L"", .ExitCode = 0});
918
+
919
+ VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_CREATE_ENV_FILE_A=create-env-file-a"));
920
+ VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_CREATE_ENV_FILE_B=create-env-file-b"));
921
+ }
922
+
923
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_EnvFile_MissingFile)
924
+ {
925
+ auto result = RunWslc(std::format(
926
+ L"container create --name {} --env-file ENV_FILE_NOT_FOUND {} env", WslcContainerName, DebianImage.NameAndTag()));
927
+ result.Verify(
928
+ {.Stderr = L"Environment file 'ENV_FILE_NOT_FOUND' cannot be opened for reading\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
929
+ EnsureContainerDoesNotExist(WslcContainerName);
930
+ }
931
+
932
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_EnvFile_InvalidContent)
933
+ {
934
+ WriteTestFile(EnvTestFile1, {"WSLC_TEST_ENV_VALID=ok", "BAD KEY=value"});
935
+
936
+ auto result = RunWslc(std::format(
937
+ L"container create --name {} --env-file {} {} env", WslcContainerName, EscapePath(EnvTestFile1.wstring()), DebianImage.NameAndTag()));
938
+ result.Verify({.Stderr = L"Environment variable key 'BAD KEY' cannot contain whitespace\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
939
+ EnsureContainerDoesNotExist(WslcContainerName);
940
+ }
941
+
942
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Publish_TCP)
943
+ {
944
+ // Port bindings only show up in inspect after start, so create then start before inspecting.
945
+ auto result = RunWslc(std::format(
946
+ L"container create --name {} -p {}:{} {} sleep 5", WslcContainerName, HostTestPort1, ContainerTestPort, DebianImage.NameAndTag()));
947
+ result.Verify({.Stderr = L"", .ExitCode = 0});
948
+
949
+ result = RunWslc(std::format(L"container start {}", WslcContainerName));
950
+ result.Verify({.Stderr = L"", .ExitCode = 0});
951
+
952
+ // Verify the port mapping is correct in the container inspect data
953
+ const auto inspect = InspectContainer(WslcContainerName);
954
+ const auto portKey = std::to_string(ContainerTestPort) + "/tcp";
955
+ VERIFY_IS_TRUE(inspect.Ports.contains(portKey));
956
+
957
+ const auto& bindings = inspect.Ports.at(portKey);
958
+ VERIFY_ARE_EQUAL(1u, bindings.size());
959
+ VERIFY_ARE_EQUAL(std::to_string(HostTestPort1), bindings[0].HostPort);
960
+ VERIFY_ARE_EQUAL("127.0.0.1", bindings[0].HostIp);
961
+ }
962
+
963
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Publish_MultipleMappings)
964
+ {
965
+ // Map two host ports to the same container port.
966
+ auto result = RunWslc(std::format(
967
+ L"container create --name {} -p {}:{} -p {}:{} {} sleep 5",
968
+ WslcContainerName,
969
+ HostTestPort1,
970
+ ContainerTestPort,
971
+ HostTestPort2,
972
+ ContainerTestPort,
973
+ DebianImage.NameAndTag()));
974
+ result.Verify({.Stderr = L"", .ExitCode = 0});
975
+
976
+ result = RunWslc(std::format(L"container start {}", WslcContainerName));
977
+ result.Verify({.Stderr = L"", .ExitCode = 0});
978
+
979
+ // Both host ports should be bound to the same container port.
980
+ const auto inspect = InspectContainer(WslcContainerName);
981
+ const auto portKey = std::to_string(ContainerTestPort) + "/tcp";
982
+ VERIFY_IS_TRUE(inspect.Ports.contains(portKey));
983
+
984
+ const auto& bindings = inspect.Ports.at(portKey);
985
+ VERIFY_ARE_EQUAL(2u, bindings.size());
986
+
987
+ bool foundHostPort1 = false;
988
+ bool foundHostPort2 = false;
989
+ for (const auto& binding : bindings)
990
+ {
991
+ if (binding.HostPort == std::to_string(HostTestPort1))
992
+ {
993
+ foundHostPort1 = true;
994
+ }
995
+ else if (binding.HostPort == std::to_string(HostTestPort2))
996
+ {
997
+ foundHostPort2 = true;
998
+ }
999
+ }
1000
+ VERIFY_IS_TRUE(foundHostPort1);
1001
+ VERIFY_IS_TRUE(foundHostPort2);
1002
+ }
1003
+
1004
+ // https://github.com/microsoft/WSL/issues/14433
1005
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_Publish_Ephemeral)
1006
+ {
1007
+ // -p <containerPort> (no host port) means the host picks a random port.
1008
+ auto result = RunWslc(std::format(
1009
+ L"container create --name {} -p {} {} sleep 5", WslcContainerName, ContainerTestPort, DebianImage.NameAndTag()));
1010
+ result.Verify({.Stderr = L"", .ExitCode = 0});
1011
+
1012
+ result = RunWslc(std::format(L"container start {}", WslcContainerName));
1013
+ result.Verify({.Stderr = L"", .ExitCode = 0});
1014
+
1015
+ // Inspect the container to verify a host port was allocated.
1016
+ const auto inspect = InspectContainer(WslcContainerName);
1017
+ const auto portKey = std::to_string(ContainerTestPort) + "/tcp";
1018
+ VERIFY_IS_TRUE(inspect.Ports.contains(portKey));
1019
+
1020
+ const auto& bindings = inspect.Ports.at(portKey);
1021
+ VERIFY_ARE_EQUAL(1u, bindings.size());
1022
+ VERIFY_IS_TRUE(std::stoi(bindings[0].HostPort) > 0);
1023
+ }
1024
+
1025
+ // https://github.com/microsoft/WSL/issues/14433
1026
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_PortUdp_NotSupported)
1027
+ {
1028
+ auto result = RunWslc(std::format(L"container create --name {} -p 80:80/udp {}", WslcContainerName, DebianImage.NameAndTag()));
1029
+ result.Verify({.Stderr = L"Port mappings with specific host IPs or UDP protocol are not currently supported\r\nError code: ERROR_NOT_SUPPORTED\r\n", .ExitCode = 1});
1030
+ EnsureContainerDoesNotExist(WslcContainerName);
1031
+ }
1032
+
1033
+ // https://github.com/microsoft/WSL/issues/14433
1034
+ WSLC_TEST_METHOD(WSLCE2E_Container_Create_PortHostIP_NotSupported)
1035
+ {
1036
+ auto result =
1037
+ RunWslc(std::format(L"container create --name {} -p 127.0.0.1:80:80 {}", WslcContainerName, DebianImage.NameAndTag()));
1038
+ result.Verify({.Stderr = L"Port mappings with specific host IPs or UDP protocol are not currently supported\r\nError code: ERROR_NOT_SUPPORTED\r\n", .ExitCode = 1});
1039
+ EnsureContainerDoesNotExist(WslcContainerName);
1040
+ }
1041
+
1042
private:
1043
// Test container name
1044
const std::wstring WslcContainerName = L"wslc-test-container";
1062
const TestImage& DebianImage = DebianTestImage();
1063
const TestImage& InvalidImage = InvalidTestImage();
1064
1065
+ // Test ports
1066
+ const uint16_t ContainerTestPort = 8080;
1067
+ const uint16_t HostTestPort1 = 1234;
1068
+ const uint16_t HostTestPort2 = 1235;
1069
+
1070
// Test volume files
1071
std::filesystem::path VolumeTestFile1;
1072
std::filesystem::path VolumeTestFile2;