diff --git a/test/windows/wslc/e2e/WSLCE2EContainerAttachTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerAttachTests.cpp new file mode 100644 index 000000000..d155a009c --- /dev/null +++ b/test/windows/wslc/e2e/WSLCE2EContainerAttachTests.cpp @@ -0,0 +1,165 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + WSLCE2EContainerAttachTests.cpp + +Abstract: + + This file contains end-to-end tests for WSLC container attach command. +--*/ + +#include "precomp.h" +#include "windows/Common.h" +#include "WSLCExecutor.h" +#include "WSLCE2EHelpers.h" + +namespace WSLCE2ETests { + +class WSLCE2EContainerAttachTests +{ + WSLC_TEST_CLASS(WSLCE2EContainerAttachTests) + + TEST_CLASS_SETUP(ClassSetup) + { + EnsureImageIsLoaded(DebianImage); + return true; + } + + TEST_CLASS_CLEANUP(ClassCleanup) + { + EnsureContainerDoesNotExist(WslcContainerName); + EnsureImageIsDeleted(DebianImage); + return true; + } + + TEST_METHOD_SETUP(TestMethodSetup) + { + EnsureContainerDoesNotExist(WslcContainerName); + return true; + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Attach_HelpCommand) + { + auto result = RunWslc(L"container attach --help"); + result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Attach_TTY) + { + VerifyContainerIsNotListed(WslcContainerName); + + const auto& prompt = ">"; + auto result = RunWslc(std::format( + L"container run -itd -e PS1={} --name {} {} bash --norc", prompt, WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + auto containerId = result.GetStdoutOneLine(); + + const auto& expectedAttachPrompt = VT::BuildContainerAttachPrompt(prompt); + const auto& expectedPrompt = VT::BuildContainerPrompt(prompt); + + auto session = RunWslcInteractive(std::format(L"container attach {}", containerId)); + VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); + + // The container attach prompt appears twice. + session.ExpectStdout(expectedAttachPrompt); + session.ExpectStdout(expectedAttachPrompt); + + session.WriteLine("echo hello"); + session.ExpectCommandEcho("echo hello"); + session.ExpectStdout("hello\r\n"); + session.ExpectStdout(expectedPrompt); + + session.WriteLine("whoami"); + session.ExpectCommandEcho("whoami"); + session.ExpectStdout("root\r\n"); + session.ExpectStdout(expectedPrompt); + + session.ExitAndVerifyNoErrors(); + auto exitCode = session.Wait(); + VERIFY_ARE_EQUAL(0, exitCode); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Attach_NoTTY) + { + VerifyContainerIsNotListed(WslcContainerName); + auto result = RunWslc(std::format(L"container run -id --name {} {} cat", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + auto containerId = result.GetStdoutOneLine(); + + auto session = RunWslcInteractive(std::format(L"container attach {}", containerId)); + VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); + + session.WriteLine("test line 1"); + session.ExpectStdout("test line 1\n"); + session.WriteLine("test line 2"); + session.ExpectStdout("test line 2\n"); + + // Close stdin to signal EOF to cat + session.CloseStdin(); + + // Wait for cat to exit with code 0 + auto exitCode = session.Wait(10000); + VERIFY_ARE_EQUAL(0, exitCode, L"Cat should exit with code 0 after receiving EOF"); + session.VerifyNoErrors(); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Attach_MissingContainerId) + { + auto result = RunWslc(L"container attach"); + result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'container-id'\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Attach_ContainerNotFound) + { + auto result = RunWslc(std::format(L"container attach {}", WslcContainerName)); + result.Verify({.Stderr = L"Element not found. \r\nError code: ERROR_NOT_FOUND\r\n", .ExitCode = 1}); + } + +private: + const std::wstring WslcContainerName = L"wslc-test-container"; + const TestImage& DebianImage = DebianTestImage(); + + std::wstring GetHelpMessage() const + { + std::wstringstream output; + output << GetWslcHeader() // + << GetDescription() // + << GetUsage() // + << GetAvailableCommands() // + << GetAvailableOptions(); + return output.str(); + } + + std::wstring GetDescription() const + { + return L"Attaches to a container.\r\n\r\n"; + } + + std::wstring GetUsage() const + { + return L"Usage: wslc container attach [] \r\n\r\n"; + } + + std::wstring GetAvailableCommands() const + { + std::wstringstream commands; + commands << L"The following arguments are available:\r\n" // + << L" container-id Container ID\r\n" // + << L"\r\n"; + return commands.str(); + } + + std::wstring GetAvailableOptions() const + { + std::wstringstream options; + options << L"The following options are available:\r\n" // + << L" --session Specify the session to use\r\n" // + << L" -h,--help Shows help about the selected command\r\n" + << L"\r\n"; + return options.str(); + } +}; +} // namespace WSLCE2ETests \ No newline at end of file diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index cda1f24b1..bc688e245 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -32,16 +32,10 @@ class WSLCE2EContainerCreateTests { EnsureImageIsLoaded(AlpineImage); EnsureImageIsLoaded(DebianImage); - EnsureImageIsLoaded(PythonImage); VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName.c_str(), HostEnvVariableValue.c_str())); VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName2.c_str(), HostEnvVariableValue2.c_str())); VERIFY_IS_TRUE(::SetEnvironmentVariableW(MissingHostEnvVariableName.c_str(), nullptr)); - - // Initialize Winsock for loopback connectivity tests - WSADATA wsaData{}; - const int result = WSAStartup(MAKEWORD(2, 2), &wsaData); - THROW_HR_IF(HRESULT_FROM_WIN32(result), result != 0); return true; } @@ -50,14 +44,10 @@ class WSLCE2EContainerCreateTests EnsureContainerDoesNotExist(WslcContainerName); EnsureImageIsDeleted(AlpineImage); EnsureImageIsDeleted(DebianImage); - EnsureImageIsDeleted(PythonImage); VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName.c_str(), nullptr)); VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName2.c_str(), nullptr)); VERIFY_IS_TRUE(::SetEnvironmentVariableW(MissingHostEnvVariableName.c_str(), nullptr)); - - // Cleanup Winsock - WSACleanup(); return true; } @@ -83,7 +73,7 @@ class WSLCE2EContainerCreateTests WSLC_TEST_METHOD(WSLCE2E_Container_Create_HelpCommand) { auto result = RunWslc(L"container create --help"); - result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0}); } WSLC_TEST_METHOD(WSLCE2E_Container_Create_MissingImage) @@ -109,7 +99,7 @@ class WSLCE2EContainerCreateTests // Create the container with a valid image auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); std::wstring containerId = result.GetStdoutOneLine(); // Verify the container is listed with the correct status @@ -122,7 +112,7 @@ class WSLCE2EContainerCreateTests // Create the container with a valid image auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); auto containerId = result.GetStdoutOneLine(); // Attempt to create another container with the same name @@ -149,7 +139,7 @@ class WSLCE2EContainerCreateTests hostDirectory.wstring(), AlpineImage.NameAndTag(), fileName)); - result.Verify({.Stdout = L"WSLC Volume Test", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"WSLC Volume Test", .Stderr = L"", .ExitCode = 0}); } WSLC_TEST_METHOD(WSLCE2E_Container_Create_Volume_WriteFromContainerReadFromHost_ReadWritePermissionByDefault) @@ -162,7 +152,7 @@ class WSLCE2EContainerCreateTests hostDirectory.wstring(), AlpineImage.NameAndTag(), fileName)); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); // Read all file content auto content = ReadFileContent(VolumeTestFile1.wstring()); @@ -179,7 +169,7 @@ class WSLCE2EContainerCreateTests hostDirectory.wstring(), AlpineImage.NameAndTag(), fileName)); - result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0}); // Read all file content std::wifstream in(VolumeTestFile1); @@ -357,460 +347,16 @@ class WSLCE2EContainerCreateTests VerifyContainerIsNotListed(WslcContainerName); auto result = RunWslc(std::format(L"container create --rm --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Start the container. result = RunWslc(std::format(L"container start {}", WslcContainerName)); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify with retry timeout of 1 minute. VerifyContainerIsNotListed(WslcContainerName, std::chrono::seconds(2), std::chrono::minutes(1)); } - WSLC_TEST_METHOD(WSLCE2E_Container_Run_Remove) - { - VerifyContainerIsNotListed(WslcContainerName); - - // Run the container with a valid image - auto result = RunWslc(std::format(L"container run --rm --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - // Run should be deleted on return so no retry. - VerifyContainerIsNotListed(WslcContainerName); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption) - { - VerifyContainerIsNotListed(WslcContainerName); - - auto result = RunWslc(std::format( - L"container run --rm --name {} -e {}=A {} env", WslcContainerName, HostEnvVariableName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}=A", HostEnvVariableName))); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_MultipleValues) - { - VerifyContainerIsNotListed(WslcContainerName); - - auto result = RunWslc(std::format( - L"container run --rm --name {} -e {}=A -e {}=B {} env", - WslcContainerName, - HostEnvVariableName, - HostEnvVariableName2, - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}=A", HostEnvVariableName))); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}=B", HostEnvVariableName2))); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_KeyOnly_UsesHostValue) - { - VerifyContainerIsNotListed(WslcContainerName); - - auto result = RunWslc(std::format( - L"container run --rm --name {} -e {} {} env", WslcContainerName, HostEnvVariableName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}={}", HostEnvVariableName, HostEnvVariableValue))); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_KeyOnly_MultipleValues_UsesHostValues) - { - VerifyContainerIsNotListed(WslcContainerName); - - auto result = RunWslc(std::format( - L"container run --rm --name {} -e {} -e {} {} env", - WslcContainerName, - HostEnvVariableName, - HostEnvVariableName2, - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}={}", HostEnvVariableName, HostEnvVariableValue))); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}={}", HostEnvVariableName2, HostEnvVariableValue2))); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_EmptyValue) - { - VerifyContainerIsNotListed(WslcContainerName); - - auto result = RunWslc(std::format( - L"container run --rm --name {} -e {}= {} env", WslcContainerName, HostEnvVariableName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}=", HostEnvVariableName))); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile) - { - VerifyContainerIsNotListed(WslcContainerName); - - WriteEnvFile(EnvTestFile1, {"WSLC_TEST_ENV_FILE_A=env-file-a", "WSLC_TEST_ENV_FILE_B=env-file-b"}); - - auto result = RunWslc(std::format( - L"container run --rm --name {} --env-file {} {} env", - WslcContainerName, - EscapePath(EnvTestFile1.wstring()), - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_FILE_A=env-file-a")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_FILE_B=env-file-b")); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_MixedWithEnvFile) - { - VerifyContainerIsNotListed(WslcContainerName); - - WriteEnvFile(EnvTestFile1, {"WSLC_TEST_ENV_MIX_FILE_A=from-file-a", "WSLC_TEST_ENV_MIX_FILE_B=from-file-b"}); - - auto result = RunWslc(std::format( - L"container run --rm --name {} -e WSLC_TEST_ENV_MIX_CLI=from-cli --env-file {} {} env", - WslcContainerName, - EscapePath(EnvTestFile1.wstring()), - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_MIX_FILE_A=from-file-a")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_MIX_FILE_B=from-file-b")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_MIX_CLI=from-cli")); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_MultipleFiles) - { - VerifyContainerIsNotListed(WslcContainerName); - - WriteEnvFile(EnvTestFile1, {"WSLC_TEST_ENV_FILE_MULTI_A=file1-a", "WSLC_TEST_ENV_FILE_MULTI_B=file1-b"}); - - WriteEnvFile(EnvTestFile2, {"WSLC_TEST_ENV_FILE_MULTI_C=file2-c", "WSLC_TEST_ENV_FILE_MULTI_D=file2-d"}); - - auto result = RunWslc(std::format( - L"container run --rm --name {} --env-file {} --env-file {} {} env", - WslcContainerName, - EscapePath(EnvTestFile1.wstring()), - EscapePath(EnvTestFile2.wstring()), - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_FILE_MULTI_A=file1-a")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_FILE_MULTI_B=file1-b")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_FILE_MULTI_C=file2-c")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_FILE_MULTI_D=file2-d")); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_MissingFile) - { - VerifyContainerIsNotListed(WslcContainerName); - - auto result = RunWslc(std::format( - L"container run --rm --name {} --env-file ENV_FILE_NOT_FOUND {} env", WslcContainerName, DebianImage.NameAndTag())); - result.Verify( - {.Stderr = L"Environment file 'ENV_FILE_NOT_FOUND' cannot be opened for reading\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_InvalidContent) - { - VerifyContainerIsNotListed(WslcContainerName); - - WriteEnvFile(EnvTestFile1, {"WSLC_TEST_ENV_VALID=ok", "BAD KEY=value"}); - - auto result = RunWslc(std::format( - L"container run --rm --name {} --env-file {} {} env", - WslcContainerName, - EscapePath(EnvTestFile1.wstring()), - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"Environment variable key 'BAD KEY' cannot contain whitespace\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_DuplicateKeys_Precedence) - { - VerifyContainerIsNotListed(WslcContainerName); - - WriteEnvFile(EnvTestFile1, {"WSLC_TEST_ENV_DUP=from-file-1"}); - - WriteEnvFile(EnvTestFile2, {"WSLC_TEST_ENV_DUP=from-file-2"}); - - // Later --env-file should win over earlier --env-file for duplicate keys - auto result = RunWslc(std::format( - L"container run --rm --name {} --env-file {} --env-file {} {} env", - WslcContainerName, - EscapePath(EnvTestFile1.wstring()), - EscapePath(EnvTestFile2.wstring()), - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_DUP=from-file-2")); - - // Explicit -e should win over env-file value for duplicate keys - result = RunWslc(std::format( - L"container run --rm --name {} -e WSLC_TEST_ENV_DUP=from-cli --env-file {} --env-file {} {} env", - WslcContainerName, - EscapePath(EnvTestFile1.wstring()), - EscapePath(EnvTestFile2.wstring()), - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_DUP=from-cli")); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_ValueContainsEquals) - { - VerifyContainerIsNotListed(WslcContainerName); - - WriteEnvFile(EnvTestFile1, {"WSLC_TEST_ENV_EQUALS=value=with=equals"}); - - auto result = RunWslc(std::format( - L"container run --rm --name {} --env-file {} {} env", - WslcContainerName, - EscapePath(EnvTestFile1.wstring()), - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_ENV_EQUALS=value=with=equals")); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption) - { - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - result = RunWslc(std::format(L"container exec -e {}=A {} env", HostEnvVariableName, WslcContainerName)); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}=A", HostEnvVariableName))); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption_KeyOnly_UsesHostValue) - { - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - result = RunWslc(std::format(L"container exec -e {} {} env", HostEnvVariableName, WslcContainerName)); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, std::format(L"{}={}", HostEnvVariableName, HostEnvVariableValue))); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvFile) - { - WriteEnvFile(EnvTestFile1, {"WSLC_TEST_EXEC_ENV_FILE_A=exec-env-file-a", "WSLC_TEST_EXEC_ENV_FILE_B=exec-env-file-b"}); - - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - result = RunWslc(std::format(L"container exec --env-file {} {} env", EscapePath(EnvTestFile1.wstring()), WslcContainerName)); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_EXEC_ENV_FILE_A=exec-env-file-a")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_EXEC_ENV_FILE_B=exec-env-file-b")); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption_MixedWithEnvFile) - { - WriteEnvFile(EnvTestFile1, {"WSLC_TEST_EXEC_ENV_MIX_FILE_A=from-file-a", "WSLC_TEST_EXEC_ENV_MIX_FILE_B=from-file-b"}); - - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - result = RunWslc(std::format( - L"container exec -e WSLC_TEST_EXEC_ENV_MIX_CLI=from-cli --env-file {} {} env", EscapePath(EnvTestFile1.wstring()), WslcContainerName)); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - const auto outputLines = result.GetStdoutLines(); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_EXEC_ENV_MIX_FILE_A=from-file-a")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_EXEC_ENV_MIX_FILE_B=from-file-b")); - VERIFY_IS_TRUE(ContainsOutputLine(outputLines, L"WSLC_TEST_EXEC_ENV_MIX_CLI=from-cli")); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvFile_MissingFile) - { - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - result = RunWslc(std::format(L"container exec --env-file ENV_FILE_NOT_FOUND {} env", WslcContainerName)); - result.Verify( - {.Stderr = L"Environment file 'ENV_FILE_NOT_FOUND' cannot be opened for reading\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_RunInteractive_TTY) - { - VerifyContainerIsNotListed(WslcContainerName); - - const auto& prompt = ">"; - auto session = RunWslcInteractive( - std::format(L"container run -it -e PS1={} --name {} {} bash --norc", prompt, WslcContainerName, DebianImage.NameAndTag())); - VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); - - const auto& expectedPrompt = VT::BuildContainerPrompt(prompt); - session.ExpectStdout(expectedPrompt); - - session.WriteLine("echo hello"); - session.ExpectCommandEcho("echo hello"); - session.ExpectStdout("hello\r\n"); - session.ExpectStdout(expectedPrompt); - - session.WriteLine("whoami"); - session.ExpectCommandEcho("whoami"); - session.ExpectStdout("root\r\n"); - session.ExpectStdout(expectedPrompt); - - auto exitCode = session.ExitAndVerifyNoErrors(); - VERIFY_ARE_EQUAL(0, exitCode); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_RunInteractive_NoTTY) - { - VerifyContainerIsNotListed(WslcContainerName); - - auto session = RunWslcInteractive(std::format(L"container run -i --name {} {} cat", WslcContainerName, DebianImage.NameAndTag())); - VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); - - session.WriteLine("test line 1"); - session.ExpectStdout("test line 1\n"); - session.WriteLine("test line 2"); - session.ExpectStdout("test line 2\n"); - - // Close stdin to signal EOF to cat - session.CloseStdin(); - - // Wait for cat to exit with code 0 - auto exitCode = session.Wait(10000); - VERIFY_ARE_EQUAL(0, exitCode, L"Cat should exit with code 0 after receiving EOF"); - session.VerifyNoErrors(); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_RunAttach_TTY) - { - VerifyContainerIsNotListed(WslcContainerName); - - const auto& prompt = ">"; - auto result = RunWslc(std::format( - L"container run -itd -e PS1={} --name {} {} bash --norc", prompt, WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - auto containerId = result.GetStdoutOneLine(); - - const auto& expectedAttachPrompt = VT::BuildContainerAttachPrompt(prompt); - const auto& expectedPrompt = VT::BuildContainerPrompt(prompt); - - auto session = RunWslcInteractive(std::format(L"container attach {}", containerId)); - VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); - - // The container attach prompt appears twice. - session.ExpectStdout(expectedAttachPrompt); - session.ExpectStdout(expectedAttachPrompt); - - session.WriteLine("echo hello"); - session.ExpectCommandEcho("echo hello"); - session.ExpectStdout("hello\r\n"); - session.ExpectStdout(expectedPrompt); - - session.WriteLine("whoami"); - session.ExpectCommandEcho("whoami"); - session.ExpectStdout("root\r\n"); - session.ExpectStdout(expectedPrompt); - - session.ExitAndVerifyNoErrors(); - auto exitCode = session.Wait(); - VERIFY_ARE_EQUAL(0, exitCode); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_RunAttach_NoTTY) - { - VerifyContainerIsNotListed(WslcContainerName); - auto result = RunWslc(std::format(L"container run -id --name {} {} cat", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - auto containerId = result.GetStdoutOneLine(); - - auto session = RunWslcInteractive(std::format(L"container attach {}", containerId)); - VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); - - session.WriteLine("test line 1"); - session.ExpectStdout("test line 1\n"); - session.WriteLine("test line 2"); - session.ExpectStdout("test line 2\n"); - - // Close stdin to signal EOF to cat - session.CloseStdin(); - - // Wait for cat to exit with code 0 - auto exitCode = session.Wait(10000); - VERIFY_ARE_EQUAL(0, exitCode, L"Cat should exit with code 0 after receiving EOF"); - session.VerifyNoErrors(); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_ExecInteractive_TTY) - { - VerifyContainerIsNotListed(WslcContainerName); - - const auto& prompt = ">"; - auto result = - RunWslc(std::format(L"container run -itd -e PS1={} --name {} {}", prompt, WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - auto containerId = result.GetStdoutOneLine(); - - const auto& expectedPrompt = VT::BuildContainerPrompt(prompt); - - auto session = RunWslcInteractive(std::format(L"container exec -it {} /bin/bash --norc", containerId)); - VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); - - session.ExpectStdout(expectedPrompt); - - session.WriteLine("echo hello"); - session.ExpectCommandEcho("echo hello"); - session.ExpectStdout("hello\r\n"); - session.ExpectStdout(expectedPrompt); - - session.WriteLine("whoami"); - session.ExpectCommandEcho("whoami"); - session.ExpectStdout("root\r\n"); - session.ExpectStdout(expectedPrompt); - - session.ExitAndVerifyNoErrors(); - auto exitCode = session.Wait(); - VERIFY_ARE_EQUAL(0, exitCode); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_ExecInteractive_NoTTY) - { - VerifyContainerIsNotListed(WslcContainerName); - auto result = RunWslc(std::format(L"container run -id --name {} {}", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - auto containerId = result.GetStdoutOneLine(); - - auto session = RunWslcInteractive(std::format(L"container exec -i {} cat", containerId)); - VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); - - session.WriteLine("test line 1"); - session.ExpectStdout("test line 1\n"); - session.WriteLine("test line 2"); - session.ExpectStdout("test line 2\n"); - - // Close stdin to signal EOF to cat - session.CloseStdin(); - - // Wait for cat to exit with code 0 - auto exitCode = session.Wait(10000); - VERIFY_ARE_EQUAL(0, exitCode, L"Cat should exit with code 0 after receiving EOF"); - session.VerifyNoErrors(); - } - WSLC_TEST_METHOD(WSLCE2E_Container_CreateStartAttach_TTY) { VerifyContainerIsNotListed(WslcContainerName); @@ -818,7 +364,7 @@ class WSLCE2EContainerCreateTests const auto& prompt = ">"; auto result = RunWslc(std::format( L"container create -it -e PS1={} --name {} {} bash --norc", prompt, WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); auto containerId = result.GetStdoutOneLine(); const auto& expectedPrompt = VT::BuildContainerPrompt(prompt); @@ -847,7 +393,7 @@ class WSLCE2EContainerCreateTests { VerifyContainerIsNotListed(WslcContainerName); auto result = RunWslc(std::format(L"container create -i --name {} {} cat", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); auto containerId = result.GetStdoutOneLine(); // Start with attach @@ -877,117 +423,30 @@ class WSLCE2EContainerCreateTests auto result = RunWslc(std::format( L"container create --name {} {} sh -c \"echo lifecycle works; exit {}\"", WslcContainerName, AlpineImage.NameAndTag(), ExpectedExitCode)); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); result.Verify({.Stdout = L"lifecycle works\n", .Stderr = L"", .ExitCode = ExpectedExitCode}); } - WSLC_TEST_METHOD(WSLCE2E_Container_Run_Port_TCP) - { - // Start a container with a simple server listening on a port - auto result = RunWslc(std::format( - L"container run -d --name {} -p {}:{} {} {}", - WslcContainerName, - HostTestPort1, - ContainerTestPort, - PythonImage.NameAndTag(), - GetPythonHttpServerScript(ContainerTestPort))); - result.Verify({.Stderr = L"", .ExitCode = 0}); - - // Verify we can connect to the server from the host side - ExpectHttpResponse(std::format(L"http://127.0.0.1:{}", HostTestPort1).c_str(), HTTP_STATUS_OK, true); - - // Verify the port mapping is correct in the container inspect data - auto inspectContainer = InspectContainer(WslcContainerName); - auto portKey = std::to_string(ContainerTestPort) + "/tcp"; - VERIFY_IS_TRUE(inspectContainer.Ports.contains(portKey)); - - auto portBindings = inspectContainer.Ports[portKey]; - VERIFY_ARE_EQUAL(1u, portBindings.size()); - VERIFY_ARE_EQUAL(std::to_string(HostTestPort1), portBindings[0].HostPort); - VERIFY_ARE_EQUAL("127.0.0.1", portBindings[0].HostIp); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortMultipleMappings) - { - // Start a container with a simple server listening on a port - // Map two host ports to the same container port - auto result = RunWslc(std::format( - L"container run -d --name {} -p {}:{} -p {}:{} {} {}", - WslcContainerName, - HostTestPort1, - ContainerTestPort, - HostTestPort2, - ContainerTestPort, - PythonImage.NameAndTag(), - GetPythonHttpServerScript(ContainerTestPort))); - result.Verify({.Stderr = L"", .ExitCode = 0}); - - // From the host side, verify we can connect to both ports - ExpectHttpResponse(std::format(L"http://127.0.0.1:{}", HostTestPort1).c_str(), HTTP_STATUS_OK, true); - ExpectHttpResponse(std::format(L"http://127.0.0.1:{}", HostTestPort2).c_str(), HTTP_STATUS_OK, true); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortAlreadyInUse) - { - // Bug: https://github.com/microsoft/WSL/issues/14448 - SKIP_TEST_NOT_IMPL(); - - // Start a container with a simple server listening on a port - auto result1 = RunWslc(std::format( - L"container run -d --name {} -p {}:{} {} {}", - WslcContainerName, - HostTestPort1, - ContainerTestPort, - PythonImage.NameAndTag(), - GetPythonHttpServerScript(ContainerTestPort))); - result1.Verify({.Stderr = L"", .ExitCode = 0}); - - // Attempt to start another container mapping the same host port - auto result2 = RunWslc(std::format(L"container run -p {}:{} {}", HostTestPort1, ContainerTestPort, DebianImage.NameAndTag())); - result2.Verify({.ExitCode = 1}); - } - - // https://github.com/microsoft/WSL/issues/14433 - WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortEphemeral_NotSupported) - { - auto result = RunWslc(std::format(L"container run -p 80 {}", DebianImage.NameAndTag())); - result.Verify({.Stderr = L"Port mappings with ephemeral host ports, specific host IPs, or UDP protocol are not currently supported\r\nError code: ERROR_NOT_SUPPORTED\r\n", .ExitCode = 1}); - } - - // https://github.com/microsoft/WSL/issues/14433 - WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortUdp_NotSupported) - { - auto result = RunWslc(std::format(L"container run -p 80:80/udp {}", DebianImage.NameAndTag())); - result.Verify({.Stderr = L"Port mappings with ephemeral host ports, specific host IPs, or UDP protocol are not currently supported\r\nError code: ERROR_NOT_SUPPORTED\r\n", .ExitCode = 1}); - } - - // https://github.com/microsoft/WSL/issues/14433 - WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortHostIP_NotSupported) - { - auto result = RunWslc(std::format(L"container run -p 127.0.0.1:80:80 {}", DebianImage.NameAndTag())); - result.Verify({.Stderr = L"Port mappings with ephemeral host ports, specific host IPs, or UDP protocol are not currently supported\r\nError code: ERROR_NOT_SUPPORTED\r\n", .ExitCode = 1}); - } - WSLC_TEST_METHOD(WSLCE2E_Container_Create_UserOption_UidRoot) { auto result = RunWslc( std::format(L"container create --name {} -u 0 {} sh -c \"id -u; id -g\"", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); - result.Verify({.Stdout = L"0\n0\n", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"0\n0\n", .Stderr = L"", .ExitCode = 0}); } WSLC_TEST_METHOD(WSLCE2E_Container_Create_UserOption_NameGroupRoot) { auto result = RunWslc(std::format( L"container create --name {} -u root:root {} sh -c \"id -un; id -u; id -g\"", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); - result.Verify({.Stdout = L"root\n0\n0\n", .Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stdout = L"root\n0\n0\n", .Stderr = L"", .ExitCode = 0}); } WSLC_TEST_METHOD(WSLCE2E_Container_Create_UserOption_UnknownUser_Fails) @@ -1001,33 +460,6 @@ class WSLCE2EContainerCreateTests {.Stderr = L"unable to find user user_does_not_exist: no matching entries in passwd file\r\nError code: E_FAIL\r\n", .ExitCode = 1}); } - WSLC_TEST_METHOD(WSLCE2E_Container_Exec_UserOption_UidRoot) - { - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - result = RunWslc(std::format(L"container exec -u 0 {} sh -c \"id -u; id -g\"", WslcContainerName)); - result.Verify({.Stdout = L"0\n0\n", .Stderr = L"", .ExitCode = S_OK}); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Exec_UserOption_NameGroupRoot) - { - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - result = RunWslc(std::format(L"container exec -u root:root {} sh -c \"id -un; id -u; id -g\"", WslcContainerName)); - result.Verify({.Stdout = L"root\n0\n0\n", .Stderr = L"", .ExitCode = S_OK}); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Exec_UserOption_InvalidGroup_Fails) - { - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); - - result = RunWslc(std::format(L"container exec -u root:badgid {} id -u", WslcContainerName)); - result.Verify({.Stdout = L"unable to find group badgid: no matching entries in group file\r\n", .ExitCode = 126}); - } - WSLC_TEST_METHOD(WSLCE2E_Container_Create_Tmpfs) { auto result = RunWslc(std::format( @@ -1099,14 +531,8 @@ class WSLCE2EContainerCreateTests // Test images const TestImage& AlpineImage = AlpineTestImage(); const TestImage& DebianImage = DebianTestImage(); - const TestImage& PythonImage = PythonTestImage(); const TestImage& InvalidImage = InvalidTestImage(); - // Test ports - const uint16_t ContainerTestPort = 8080; - const uint16_t HostTestPort1 = 1234; - const uint16_t HostTestPort2 = 1235; - // Test volume files std::filesystem::path VolumeTestFile1; std::filesystem::path VolumeTestFile2; @@ -1162,34 +588,5 @@ class WSLCE2EContainerCreateTests << L"\r\n"; return options.str(); } - - void WriteEnvFile(const std::filesystem::path& filePath, const std::vector& envVariableLines) const - { - std::ofstream envFile(filePath, std::ios::out | std::ios::trunc | std::ios::binary); - VERIFY_IS_TRUE(envFile.is_open()); - for (const auto& line : envVariableLines) - { - envFile << line << "\n"; - } - VERIFY_IS_TRUE(envFile.good()); - } - - bool ContainsOutputLine(const std::vector& outputLines, const std::wstring& expectedLine) const - { - for (const auto& line : outputLines) - { - if (line == expectedLine) - { - return true; - } - } - - return false; - } - - std::wstring GetPythonHttpServerScript(uint16_t port) - { - return std::format(L"python3 -m http.server {}", port); - } }; } // namespace WSLCE2ETests \ No newline at end of file diff --git a/test/windows/wslc/e2e/WSLCE2EContainerExecTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerExecTests.cpp index 917ff3c4d..f35ca6f40 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerExecTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerExecTests.cpp @@ -24,6 +24,10 @@ class WSLCE2EContainerExecTests TEST_CLASS_SETUP(ClassSetup) { + VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName.c_str(), HostEnvVariableValue.c_str())); + VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName2.c_str(), HostEnvVariableValue2.c_str())); + VERIFY_IS_TRUE(::SetEnvironmentVariableW(MissingHostEnvVariableName.c_str(), nullptr)); + EnsureImageIsLoaded(DebianImage); return true; } @@ -32,21 +36,343 @@ class WSLCE2EContainerExecTests { EnsureContainerDoesNotExist(WslcContainerName); EnsureImageIsDeleted(DebianImage); + + VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName.c_str(), nullptr)); + VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName2.c_str(), nullptr)); + VERIFY_IS_TRUE(::SetEnvironmentVariableW(MissingHostEnvVariableName.c_str(), nullptr)); return true; } TEST_METHOD_SETUP(TestMethodSetup) { + EnvTestFile1 = wsl::windows::common::filesystem::GetTempFilename(); + EnvTestFile2 = wsl::windows::common::filesystem::GetTempFilename(); EnsureContainerDoesNotExist(WslcContainerName); return true; } + TEST_METHOD_CLEANUP(TestMethodCleanup) + { + DeleteFileW(EnvTestFile1.c_str()); + DeleteFileW(EnvTestFile2.c_str()); + return true; + } + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_HelpCommand) { auto result = RunWslc(L"container exec --help"); result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0}); } + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_MissingContainerId) + { + auto result = RunWslc(L"container exec"); + result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'container-id'\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_MissingCommand) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec {}", WslcContainerName)); + result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"Required argument not provided: 'command'\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_ContainerNotFound) + { + auto result = RunWslc(std::format(L"container exec {} echo hello", WslcContainerName)); + result.Verify({.Stderr = L"Element not found. \r\nError code: ERROR_NOT_FOUND\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_SimpleCommand) + { + // Run a container in background + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + // Execute a command + result = RunWslc(std::format(L"container exec {} echo hello", WslcContainerName)); + result.Verify({.Stdout = L"hello\n", .Stderr = L"", .ExitCode = 0}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_InteractiveTTY) + { + VerifyContainerIsNotListed(WslcContainerName); + + const auto& prompt = ">"; + auto result = + RunWslc(std::format(L"container run -itd -e PS1={} --name {} {}", prompt, WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + auto containerId = result.GetStdoutOneLine(); + + const auto& expectedPrompt = VT::BuildContainerPrompt(prompt); + + auto session = RunWslcInteractive(std::format(L"container exec -it {} /bin/bash --norc", containerId)); + VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); + + session.ExpectStdout(expectedPrompt); + + session.WriteLine("echo hello"); + session.ExpectCommandEcho("echo hello"); + session.ExpectStdout("hello\r\n"); + session.ExpectStdout(expectedPrompt); + + session.WriteLine("whoami"); + session.ExpectCommandEcho("whoami"); + session.ExpectStdout("root\r\n"); + session.ExpectStdout(expectedPrompt); + + session.ExitAndVerifyNoErrors(); + auto exitCode = session.Wait(); + VERIFY_ARE_EQUAL(0, exitCode); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_InteractiveNoTTY) + { + VerifyContainerIsNotListed(WslcContainerName); + auto result = RunWslc(std::format(L"container run -id --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + auto containerId = result.GetStdoutOneLine(); + + auto session = RunWslcInteractive(std::format(L"container exec -i {} cat", containerId)); + VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); + + session.WriteLine("test line 1"); + session.ExpectStdout("test line 1\n"); + session.WriteLine("test line 2"); + session.ExpectStdout("test line 2\n"); + + // Close stdin to signal EOF to cat + session.CloseStdin(); + + // Wait for cat to exit with code 0 + auto exitCode = session.Wait(10000); + VERIFY_ARE_EQUAL(0, exitCode, L"Cat should exit with code 0 after receiving EOF"); + session.VerifyNoErrors(); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec -e {}=A {} env", HostEnvVariableName, WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}=A", HostEnvVariableName))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption_KeyOnly_UsesHostValue) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec -e {} {} env", HostEnvVariableName, WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}={}", HostEnvVariableName, HostEnvVariableValue))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvFile) + { + WriteTestFile(EnvTestFile1, {"WSLC_TEST_EXEC_ENV_FILE_A=exec-env-file-a", "WSLC_TEST_EXEC_ENV_FILE_B=exec-env-file-b"}); + + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec --env-file {} {} env", EscapePath(EnvTestFile1.wstring()), WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_FILE_A=exec-env-file-a")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_FILE_B=exec-env-file-b")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption_MultipleValues) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format( + L"container exec -e {}=value-a -e {}=value-b {} env", HostEnvVariableName, HostEnvVariableName2, WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}=value-a", HostEnvVariableName))); + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}=value-b", HostEnvVariableName2))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption_KeyOnly_MultipleValues_UsesHostValues) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec -e {} -e {} {} env", HostEnvVariableName, HostEnvVariableName2, WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}={}", HostEnvVariableName, HostEnvVariableValue))); + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}={}", HostEnvVariableName2, HostEnvVariableValue2))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption_EmptyValue) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + // Pass an explicit empty value and verify it is present as KEY= + result = RunWslc(std::format(L"container exec -e {}= {} env", HostEnvVariableName, WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}=", HostEnvVariableName))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvOption_MixedWithEnvFile) + { + WriteTestFile(EnvTestFile1, {"WSLC_TEST_EXEC_ENV_MIX_FILE_A=from-file-a", "WSLC_TEST_EXEC_ENV_MIX_FILE_B=from-file-b"}); + + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format( + L"container exec -e WSLC_TEST_EXEC_ENV_MIX_CLI=from-cli --env-file {} {} env", EscapePath(EnvTestFile1.wstring()), WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_MIX_FILE_A=from-file-a")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_MIX_FILE_B=from-file-b")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_MIX_CLI=from-cli")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvFile_MissingFile) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec --env-file ENV_FILE_NOT_FOUND {} env", WslcContainerName)); + result.Verify( + {.Stderr = L"Environment file 'ENV_FILE_NOT_FOUND' cannot be opened for reading\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvFile_MultipleFiles) + { + WriteTestFile(EnvTestFile1, {"WSLC_TEST_EXEC_ENV_FILE_MULTI_A=file1-a", "WSLC_TEST_EXEC_ENV_FILE_MULTI_B=file1-b"}); + WriteTestFile(EnvTestFile2, {"WSLC_TEST_EXEC_ENV_FILE_MULTI_C=file2-c", "WSLC_TEST_EXEC_ENV_FILE_MULTI_D=file2-d"}); + + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format( + L"container exec --env-file {} --env-file {} {} env", EscapePath(EnvTestFile1.wstring()), EscapePath(EnvTestFile2.wstring()), WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_FILE_MULTI_A=file1-a")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_FILE_MULTI_B=file1-b")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_FILE_MULTI_C=file2-c")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_FILE_MULTI_D=file2-d")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvFile_InvalidContent) + { + WriteTestFile(EnvTestFile1, {"WSLC_TEST_EXEC_ENV_VALID=ok", "BAD KEY=value"}); + + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec --env-file {} {} env", EscapePath(EnvTestFile1.wstring()), WslcContainerName)); + result.Verify({.Stderr = L"Environment variable key 'BAD KEY' cannot contain whitespace\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvFile_DuplicateKeys_Precedence) + { + WriteTestFile(EnvTestFile1, {"WSLC_TEST_EXEC_ENV_DUP=from-file-1"}); + WriteTestFile(EnvTestFile2, {"WSLC_TEST_EXEC_ENV_DUP=from-file-2"}); + + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + // Later --env-file wins + result = RunWslc(std::format( + L"container exec --env-file {} --env-file {} {} env", EscapePath(EnvTestFile1.wstring()), EscapePath(EnvTestFile2.wstring()), WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_DUP=from-file-2")); + + // Explicit -e wins over env-file + result = RunWslc(std::format( + L"container exec -e WSLC_TEST_EXEC_ENV_DUP=from-cli --env-file {} --env-file {} {} env", + EscapePath(EnvTestFile1.wstring()), + EscapePath(EnvTestFile2.wstring()), + WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_DUP=from-cli")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_EnvFile_ValueContainsEquals) + { + WriteTestFile(EnvTestFile1, {"WSLC_TEST_EXEC_ENV_EQUALS=value=with=equals"}); + + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec --env-file {} {} env", EscapePath(EnvTestFile1.wstring()), WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_EXEC_ENV_EQUALS=value=with=equals")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_ExitCode_Propagates) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec {} sh -c \"exit 42\"", WslcContainerName)); + result.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 42}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_Stderr_Propagates) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec {} sh -c \"echo exec-error 1>&2\"", WslcContainerName)); + result.Verify({.Stdout = L"", .Stderr = L"exec-error\n", .ExitCode = 0}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_StoppedContainer) + { + auto result = RunWslc(std::format(L"container run --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec {} echo should-fail", WslcContainerName)); + result.Verify({.Stderr = L"The group or resource is not in the correct state to perform the requested operation. \r\nError code: ERROR_INVALID_STATE\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_UserOption_UidRoot) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec -u 0 {} sh -c \"id -u; id -g\"", WslcContainerName)); + result.Verify({.Stdout = L"0\n0\n", .Stderr = L"", .ExitCode = 0}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_UserOption_NameGroupRoot) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec -u root:root {} sh -c \"id -un; id -u; id -g\"", WslcContainerName)); + result.Verify({.Stdout = L"root\n0\n0\n", .Stderr = L"", .ExitCode = 0}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_UserOption_InvalidGroup_Fails) + { + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container exec -u root:badgid {} id -u", WslcContainerName)); + result.Verify({.Stdout = L"unable to find group badgid: no matching entries in group file\r\n", .ExitCode = 126}); + } + WSLC_TEST_METHOD(WSLCE2E_Container_Exec_WorkDir) { auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); @@ -69,6 +395,17 @@ class WSLCE2EContainerExecTests const std::wstring WslcContainerName = L"wslc-test-container"; const TestImage& DebianImage = DebianTestImage(); + // Test environment variables + const std::wstring HostEnvVariableName = L"WSLC_TEST_HOST_ENV"; + const std::wstring HostEnvVariableName2 = L"WSLC_TEST_HOST_ENV2"; + const std::wstring HostEnvVariableValue = L"wslc-host-env-value"; + const std::wstring HostEnvVariableValue2 = L"wslc-host-env-value2"; + const std::wstring MissingHostEnvVariableName = L"WSLC_TEST_MISSING_HOST_ENV"; + + // Test environment variable files + std::filesystem::path EnvTestFile1; + std::filesystem::path EnvTestFile2; + std::wstring GetHelpMessage() const { std::wstringstream output; diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 85f166890..831854110 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -25,6 +25,15 @@ class WSLCE2EContainerRunTests TEST_CLASS_SETUP(ClassSetup) { EnsureImageIsLoaded(DebianImage); + EnsureImageIsLoaded(PythonImage); + + VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName.c_str(), HostEnvVariableValue.c_str())); + VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName2.c_str(), HostEnvVariableValue2.c_str())); + + // Initialize Winsock for loopback connectivity tests + WSADATA wsaData{}; + const int result = WSAStartup(MAKEWORD(2, 2), &wsaData); + THROW_HR_IF(HRESULT_FROM_WIN32(result), result != 0); return true; } @@ -32,12 +41,29 @@ class WSLCE2EContainerRunTests { EnsureContainerDoesNotExist(WslcContainerName); EnsureImageIsDeleted(DebianImage); + EnsureImageIsDeleted(PythonImage); + + VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName.c_str(), nullptr)); + VERIFY_IS_TRUE(::SetEnvironmentVariableW(HostEnvVariableName2.c_str(), nullptr)); + + // Cleanup Winsock + WSACleanup(); return true; } TEST_METHOD_SETUP(TestMethodSetup) { EnsureContainerDoesNotExist(WslcContainerName); + + EnvTestFile1 = wsl::windows::common::filesystem::GetTempFilename(); + EnvTestFile2 = wsl::windows::common::filesystem::GetTempFilename(); + return true; + } + + TEST_METHOD_CLEANUP(TestMethodCleanup) + { + DeleteFileW(EnvTestFile1.c_str()); + DeleteFileW(EnvTestFile2.c_str()); return true; } @@ -87,6 +113,211 @@ class WSLCE2EContainerRunTests VerifyContainerIsListed(WslcContainerName, L"running"); } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Remove) + { + VerifyContainerIsNotListed(WslcContainerName); + + // Run the container with a valid image + auto result = RunWslc(std::format(L"container run --rm --name {} {} echo hello", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + // Run should be deleted on return so no retry. + VerifyContainerIsNotListed(WslcContainerName); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption) + { + VerifyContainerIsNotListed(WslcContainerName); + + auto result = RunWslc(std::format( + L"container run --rm --name {} -e {}=A {} env", WslcContainerName, HostEnvVariableName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}=A", HostEnvVariableName))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_MultipleValues) + { + VerifyContainerIsNotListed(WslcContainerName); + + auto result = RunWslc(std::format( + L"container run --rm --name {} -e {}=A -e {}=B {} env", + WslcContainerName, + HostEnvVariableName, + HostEnvVariableName2, + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}=A", HostEnvVariableName))); + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}=B", HostEnvVariableName2))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_KeyOnly_UsesHostValue) + { + VerifyContainerIsNotListed(WslcContainerName); + + auto result = RunWslc(std::format( + L"container run --rm --name {} -e {} {} env", WslcContainerName, HostEnvVariableName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}={}", HostEnvVariableName, HostEnvVariableValue))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_KeyOnly_MultipleValues_UsesHostValues) + { + VerifyContainerIsNotListed(WslcContainerName); + + auto result = RunWslc(std::format( + L"container run --rm --name {} -e {} -e {} {} env", + WslcContainerName, + HostEnvVariableName, + HostEnvVariableName2, + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}={}", HostEnvVariableName, HostEnvVariableValue))); + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}={}", HostEnvVariableName2, HostEnvVariableValue2))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_EmptyValue) + { + VerifyContainerIsNotListed(WslcContainerName); + + auto result = RunWslc(std::format( + L"container run --rm --name {} -e {}= {} env", WslcContainerName, HostEnvVariableName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(std::format(L"{}=", HostEnvVariableName))); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile) + { + VerifyContainerIsNotListed(WslcContainerName); + + WriteTestFile(EnvTestFile1, {"WSLC_TEST_ENV_FILE_A=env-file-a", "WSLC_TEST_ENV_FILE_B=env-file-b"}); + + auto result = RunWslc(std::format( + L"container run --rm --name {} --env-file {} {} env", + WslcContainerName, + EscapePath(EnvTestFile1.wstring()), + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_FILE_A=env-file-a")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_FILE_B=env-file-b")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvOption_MixedWithEnvFile) + { + VerifyContainerIsNotListed(WslcContainerName); + + WriteTestFile(EnvTestFile1, {"WSLC_TEST_ENV_MIX_FILE_A=from-file-a", "WSLC_TEST_ENV_MIX_FILE_B=from-file-b"}); + + auto result = RunWslc(std::format( + L"container run --rm --name {} -e WSLC_TEST_ENV_MIX_CLI=from-cli --env-file {} {} env", + WslcContainerName, + EscapePath(EnvTestFile1.wstring()), + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_MIX_FILE_A=from-file-a")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_MIX_FILE_B=from-file-b")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_MIX_CLI=from-cli")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_MultipleFiles) + { + VerifyContainerIsNotListed(WslcContainerName); + + WriteTestFile(EnvTestFile1, {"WSLC_TEST_ENV_FILE_MULTI_A=file1-a", "WSLC_TEST_ENV_FILE_MULTI_B=file1-b"}); + + WriteTestFile(EnvTestFile2, {"WSLC_TEST_ENV_FILE_MULTI_C=file2-c", "WSLC_TEST_ENV_FILE_MULTI_D=file2-d"}); + + auto result = RunWslc(std::format( + L"container run --rm --name {} --env-file {} --env-file {} {} env", + WslcContainerName, + EscapePath(EnvTestFile1.wstring()), + EscapePath(EnvTestFile2.wstring()), + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_FILE_MULTI_A=file1-a")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_FILE_MULTI_B=file1-b")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_FILE_MULTI_C=file2-c")); + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_FILE_MULTI_D=file2-d")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_MissingFile) + { + VerifyContainerIsNotListed(WslcContainerName); + + auto result = RunWslc(std::format( + L"container run --rm --name {} --env-file ENV_FILE_NOT_FOUND {} env", WslcContainerName, DebianImage.NameAndTag())); + result.Verify( + {.Stderr = L"Environment file 'ENV_FILE_NOT_FOUND' cannot be opened for reading\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_InvalidContent) + { + VerifyContainerIsNotListed(WslcContainerName); + + WriteTestFile(EnvTestFile1, {"WSLC_TEST_ENV_VALID=ok", "BAD KEY=value"}); + + auto result = RunWslc(std::format( + L"container run --rm --name {} --env-file {} {} env", + WslcContainerName, + EscapePath(EnvTestFile1.wstring()), + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Environment variable key 'BAD KEY' cannot contain whitespace\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_DuplicateKeys_Precedence) + { + VerifyContainerIsNotListed(WslcContainerName); + + WriteTestFile(EnvTestFile1, {"WSLC_TEST_ENV_DUP=from-file-1"}); + + WriteTestFile(EnvTestFile2, {"WSLC_TEST_ENV_DUP=from-file-2"}); + + // Later --env-file should win over earlier --env-file for duplicate keys + auto result = RunWslc(std::format( + L"container run --rm --name {} --env-file {} --env-file {} {} env", + WslcContainerName, + EscapePath(EnvTestFile1.wstring()), + EscapePath(EnvTestFile2.wstring()), + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_DUP=from-file-2")); + + // Explicit -e should win over env-file value for duplicate keys + result = RunWslc(std::format( + L"container run --rm --name {} -e WSLC_TEST_ENV_DUP=from-cli --env-file {} --env-file {} {} env", + WslcContainerName, + EscapePath(EnvTestFile1.wstring()), + EscapePath(EnvTestFile2.wstring()), + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_DUP=from-cli")); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_EnvFile_ValueContainsEquals) + { + VerifyContainerIsNotListed(WslcContainerName); + + WriteTestFile(EnvTestFile1, {"WSLC_TEST_ENV_EQUALS=value=with=equals"}); + + auto result = RunWslc(std::format( + L"container run --rm --name {} --env-file {} {} env", + WslcContainerName, + EscapePath(EnvTestFile1.wstring()), + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + VERIFY_IS_TRUE(result.StdoutContainsLine(L"WSLC_TEST_ENV_EQUALS=value=with=equals")); + } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_UserOption_NameRoot) { auto result = RunWslc(std::format(L"container run --rm -u root {} sh -c \"id -un; id -u; id -g\"", DebianImage.NameAndTag())); @@ -131,6 +362,140 @@ class WSLCE2EContainerRunTests result.Verify({.Stdout = L"nobody\n65534\n65534\n", .Stderr = L"", .ExitCode = 0}); } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortMultipleMappings) + { + // Start a container with a simple server listening on a port + // Map two host ports to the same container port + auto result = RunWslc(std::format( + L"container run -d --name {} -p {}:{} -p {}:{} {} {}", + WslcContainerName, + HostTestPort1, + ContainerTestPort, + HostTestPort2, + ContainerTestPort, + PythonImage.NameAndTag(), + GetPythonHttpServerScript(ContainerTestPort))); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + // From the host side, verify we can connect to both ports + ExpectHttpResponse(std::format(L"http://127.0.0.1:{}", HostTestPort1).c_str(), HTTP_STATUS_OK, true); + ExpectHttpResponse(std::format(L"http://127.0.0.1:{}", HostTestPort2).c_str(), HTTP_STATUS_OK, true); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortAlreadyInUse) + { + // Bug: https://github.com/microsoft/WSL/issues/14448 + SKIP_TEST_NOT_IMPL(); + + // Start a container with a simple server listening on a port + auto result1 = RunWslc(std::format( + L"container run -d --name {} -p {}:{} {} {}", + WslcContainerName, + HostTestPort1, + ContainerTestPort, + PythonImage.NameAndTag(), + GetPythonHttpServerScript(ContainerTestPort))); + result1.Verify({.Stderr = L"", .ExitCode = 0}); + + // Attempt to start another container mapping the same host port + auto result2 = RunWslc(std::format(L"container run -p {}:{} {}", HostTestPort1, ContainerTestPort, DebianImage.NameAndTag())); + result2.Verify({.ExitCode = 1}); + } + + // https://github.com/microsoft/WSL/issues/14433 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortEphemeral_NotSupported) + { + auto result = RunWslc(std::format(L"container run -p 80 {}", DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Port mappings with ephemeral host ports, specific host IPs, or UDP protocol are not currently supported\r\nError code: ERROR_NOT_SUPPORTED\r\n", .ExitCode = 1}); + } + + // https://github.com/microsoft/WSL/issues/14433 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortUdp_NotSupported) + { + auto result = RunWslc(std::format(L"container run -p 80:80/udp {}", DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Port mappings with ephemeral host ports, specific host IPs, or UDP protocol are not currently supported\r\nError code: ERROR_NOT_SUPPORTED\r\n", .ExitCode = 1}); + } + + // https://github.com/microsoft/WSL/issues/14433 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_PortHostIP_NotSupported) + { + auto result = RunWslc(std::format(L"container run -p 127.0.0.1:80:80 {}", DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Port mappings with ephemeral host ports, specific host IPs, or UDP protocol are not currently supported\r\nError code: ERROR_NOT_SUPPORTED\r\n", .ExitCode = 1}); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Port_TCP) + { + // Start a container with a simple server listening on a port + auto result = RunWslc(std::format( + L"container run -d --name {} -p {}:{} {} {}", + WslcContainerName, + HostTestPort1, + ContainerTestPort, + PythonImage.NameAndTag(), + GetPythonHttpServerScript(ContainerTestPort))); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + // Verify we can connect to the server from the host side + ExpectHttpResponse(std::format(L"http://127.0.0.1:{}", HostTestPort1).c_str(), HTTP_STATUS_OK, true); + + // Verify the port mapping is correct in the container inspect data + auto inspectContainer = InspectContainer(WslcContainerName); + auto portKey = std::to_string(ContainerTestPort) + "/tcp"; + VERIFY_IS_TRUE(inspectContainer.Ports.contains(portKey)); + + auto portBindings = inspectContainer.Ports[portKey]; + VERIFY_ARE_EQUAL(1u, portBindings.size()); + VERIFY_ARE_EQUAL(std::to_string(HostTestPort1), portBindings[0].HostPort); + VERIFY_ARE_EQUAL("127.0.0.1", portBindings[0].HostIp); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Interactive_TTY) + { + VerifyContainerIsNotListed(WslcContainerName); + + const auto& prompt = ">"; + auto session = RunWslcInteractive( + std::format(L"container run -it -e PS1={} --name {} {} bash --norc", prompt, WslcContainerName, DebianImage.NameAndTag())); + VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); + + const auto& expectedPrompt = VT::BuildContainerPrompt(prompt); + session.ExpectStdout(expectedPrompt); + + session.WriteLine("echo hello"); + session.ExpectCommandEcho("echo hello"); + session.ExpectStdout("hello\r\n"); + session.ExpectStdout(expectedPrompt); + + session.WriteLine("whoami"); + session.ExpectCommandEcho("whoami"); + session.ExpectStdout("root\r\n"); + session.ExpectStdout(expectedPrompt); + + auto exitCode = session.ExitAndVerifyNoErrors(); + VERIFY_ARE_EQUAL(0, exitCode); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Interactive_NoTTY) + { + VerifyContainerIsNotListed(WslcContainerName); + + auto session = RunWslcInteractive(std::format(L"container run -i --name {} {} cat", WslcContainerName, DebianImage.NameAndTag())); + VERIFY_IS_TRUE(session.IsRunning(), L"Container session should be running"); + + session.WriteLine("test line 1"); + session.ExpectStdout("test line 1\n"); + session.WriteLine("test line 2"); + session.ExpectStdout("test line 2\n"); + + // Close stdin to signal EOF to cat + session.CloseStdin(); + + // Wait for cat to exit with code 0 + auto exitCode = session.Wait(10000); + VERIFY_ARE_EQUAL(0, exitCode, L"Cat should exit with code 0 after receiving EOF"); + session.VerifyNoErrors(); + } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_Tmpfs) { auto result = RunWslc(std::format( @@ -171,8 +536,27 @@ class WSLCE2EContainerRunTests } private: + // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; + + // Test environment variables + const std::wstring HostEnvVariableName = L"WSLC_TEST_HOST_ENV"; + const std::wstring HostEnvVariableName2 = L"WSLC_TEST_HOST_ENV2"; + const std::wstring HostEnvVariableValue = L"wslc-host-env-value"; + const std::wstring HostEnvVariableValue2 = L"wslc-host-env-value2"; + + // Test images const TestImage& DebianImage = DebianTestImage(); + const TestImage& PythonImage = PythonTestImage(); + + // Test environment variable files + std::filesystem::path EnvTestFile1; + std::filesystem::path EnvTestFile2; + + // Test ports + const uint16_t ContainerTestPort = 8080; + const uint16_t HostTestPort1 = 1234; + const uint16_t HostTestPort2 = 1235; std::wstring GetHelpMessage() const { diff --git a/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp index e40dedd2a..cd855ba81 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp @@ -36,7 +36,7 @@ class WSLCE2EContainerTests WSLC_TEST_METHOD(WSLCE2E_Container_HelpCommand) { - RunWslc(L"container --help").Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = S_OK}); + RunWslc(L"container --help").Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0}); } WSLC_TEST_METHOD(WSLCE2E_Container_InvalidCommand_DisplaysErrorMessage) diff --git a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp index d0fff2ab1..bc62a17d1 100644 --- a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp @@ -63,11 +63,11 @@ class WSLCE2EGlobalTests { // Run container list to create the default elevated session auto result = RunWslc(L"container list", ElevationType::Elevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session list shows the admin session name result = RunWslc(L"session list", ElevationType::Elevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos); @@ -77,11 +77,11 @@ class WSLCE2EGlobalTests { // Run container list non-elevated to create the default non-elevated session auto result = RunWslc(L"container list", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session list shows the non-admin session name result = RunWslc(L"session list", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); @@ -93,7 +93,7 @@ class WSLCE2EGlobalTests { // First ensure admin session is created by running container list. auto result = RunWslc(L"container list", ElevationType::Elevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Try to explicitly target the admin session from non-elevated process result = RunWslc(L"container list --session wslc-cli-admin", ElevationType::NonElevated); @@ -106,13 +106,13 @@ class WSLCE2EGlobalTests { // First ensure non-elevated session is created by running container list. auto result = RunWslc(L"container list", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Elevated user should be able to explicitly target the non-admin session result = RunWslc(L"container list --session wslc-cli", ElevationType::Elevated); // This should work - elevated users can access non-elevated sessions - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); } WSLC_TEST_METHOD(WSLCE2E_Session_CreateMixedElevation_Fails) @@ -133,21 +133,21 @@ class WSLCE2EGlobalTests { // Run container list to create the default session if it does not already exist auto result = RunWslc(L"container list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session list shows the admin session name result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos); // Terminate the session result = RunWslc(L"session terminate"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session no longer shows up result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos); @@ -155,21 +155,21 @@ class WSLCE2EGlobalTests // Run container list to create the default session if it does not already exist result = RunWslc(L"container list", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session list shows the non-elevated session name result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos); // Terminate the session result = RunWslc(L"session terminate", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session no longer shows up result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos); } @@ -178,21 +178,21 @@ class WSLCE2EGlobalTests { // Run container list to create the default session if it does not already exist auto result = RunWslc(L"container list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session list shows the admin session name result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos); // Terminate the session result = RunWslc(L"session terminate wslc-cli-admin"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session no longer shows up result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos); @@ -200,21 +200,21 @@ class WSLCE2EGlobalTests // Run container list to create the default session if it does not already exist result = RunWslc(L"container list", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session list shows the non-elevated session name result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos); // Terminate the session result = RunWslc(L"session terminate wslc-cli", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session no longer shows up result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos); } @@ -223,13 +223,13 @@ class WSLCE2EGlobalTests { // Run container list to create the default sessions if they do not already exist. auto result = RunWslc(L"container list", ElevationType::Elevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); result = RunWslc(L"container list", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify session list shows both sessions. result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli-admin") != std::wstring::npos); VERIFY_IS_TRUE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos); @@ -240,11 +240,11 @@ class WSLCE2EGlobalTests // Terminate the non-elevated session from the elevated process. result = RunWslc(L"session terminate wslc-cli", ElevationType::Elevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify non-elevated session no longer shows up result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); VERIFY_IS_TRUE(result.Stdout.has_value()); VERIFY_IS_FALSE(result.Stdout->find(L"wslc-cli\r\n") != std::wstring::npos); } @@ -269,7 +269,7 @@ class WSLCE2EGlobalTests // Verify session list result = RunWslc(L"session list"); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Verify there is a session with the name of the test session in the session list output. VERIFY_IS_TRUE(result.Stdout.has_value()); @@ -278,13 +278,13 @@ class WSLCE2EGlobalTests // Run container list in the test session, which should succeed if the session is valid. result = RunWslc(std::format(L"container list --session {}", session.Name())); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); // Add a container to the new session. result = RunWslc( std::format(L"container create --session {} --name {} {}", session.Name(), L"test-cont", DebianTestImage().NameAndTag())); result.Dump(); // Dump so it is easier to find any potential issues with the pull in the test output. - result.Verify({.ExitCode = S_OK}); + result.Verify({.ExitCode = 0}); // Verify container exists in the custom session VerifyContainerIsListed(L"test-cont", L"created", session.Name()); @@ -297,9 +297,9 @@ class WSLCE2EGlobalTests { // Ensure sessions are created by running container list elevated and non-elevated. auto result = RunWslc(L"container list", ElevationType::NonElevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); result = RunWslc(L"container list", ElevationType::Elevated); - result.Verify({.Stderr = L"", .ExitCode = S_OK}); + result.Verify({.Stderr = L"", .ExitCode = 0}); { Log::Comment(L"Testing elevated interactive session"); diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp index cec283e85..7f8a2667b 100644 --- a/test/windows/wslc/e2e/WSLCE2EHelpers.cpp +++ b/test/windows/wslc/e2e/WSLCE2EHelpers.cpp @@ -341,4 +341,21 @@ void EnsureSessionIsTerminated(const std::wstring& sessionName) } } } + +void WriteTestFile(const std::filesystem::path& filePath, const std::vector& lines) +{ + std::ofstream file(filePath, std::ios::out | std::ios::trunc | std::ios::binary); + VERIFY_IS_TRUE(file.is_open()); + for (const auto& line : lines) + { + file << line << "\n"; + } + + VERIFY_IS_TRUE(file.good()); +} + +std::wstring GetPythonHttpServerScript(uint16_t port) +{ + return std::format(L"python3 -m http.server {}", port); +} } // namespace WSLCE2ETests \ No newline at end of file diff --git a/test/windows/wslc/e2e/WSLCE2EHelpers.h b/test/windows/wslc/e2e/WSLCE2EHelpers.h index 9a4ea4a7d..ffdf30e16 100644 --- a/test/windows/wslc/e2e/WSLCE2EHelpers.h +++ b/test/windows/wslc/e2e/WSLCE2EHelpers.h @@ -131,6 +131,9 @@ void EnsureImageIsDeleted(const TestImage& image); void EnsureImageContainersAreDeleted(const TestImage& image); void EnsureSessionIsTerminated(const std::wstring& sessionName = L""); +void WriteTestFile(const std::filesystem::path& filePath, const std::vector& envVariableLines); +std::wstring GetPythonHttpServerScript(uint16_t port); + // Default timeout of 0 will execute once. template void VerifyContainerIsNotListed( diff --git a/test/windows/wslc/e2e/WSLCExecutor.cpp b/test/windows/wslc/e2e/WSLCExecutor.cpp index ba855dfd5..3472d4eba 100644 --- a/test/windows/wslc/e2e/WSLCExecutor.cpp +++ b/test/windows/wslc/e2e/WSLCExecutor.cpp @@ -127,6 +127,20 @@ std::wstring WSLCExecutionResult::GetStdoutOneLine() const return stdoutLines[0]; } +bool WSLCExecutionResult::StdoutContainsLine(const std::wstring& expectedLine) const +{ + VERIFY_IS_TRUE(Stdout.has_value()); + for (const auto& line : GetStdoutLines()) + { + if (line == expectedLine) + { + return true; + } + } + + return false; +} + WSLCExecutionResult RunWslc(const std::wstring& commandLine, ElevationType elevationType) { auto cmd = L"\"" + GetWslcPath() + L"\" " + commandLine; diff --git a/test/windows/wslc/e2e/WSLCExecutor.h b/test/windows/wslc/e2e/WSLCExecutor.h index 2c3c54ea2..5f1612f71 100644 --- a/test/windows/wslc/e2e/WSLCExecutor.h +++ b/test/windows/wslc/e2e/WSLCExecutor.h @@ -43,6 +43,7 @@ struct WSLCExecutionResult void Verify(const WSLCExecutionResult& expected) const; std::vector GetStdoutLines() const; std::wstring GetStdoutOneLine() const; + bool StdoutContainsLine(const std::wstring& expectedLine) const; }; // Interactive session for testing wslc commands that require stdin/stdout interaction.