@samitouri / QOSAMI-WSL / commits / a63f0b82

Implement skipped wslc E2E tests (#41243)

AmirMS committed Aug 4, 2026 at 20:17 UTC a63f0b82eb6d414c9e181393b7fa803c24150eb3
5 files changed +67 -37
test/windows/wslc/e2e/WSLCE2EHelpers.cpp
+24 -18
@@ -703,28 +703,34 @@ namespace {
703
704 void WaitForTtySize(const WSLCInteractiveSession& session, SHORT columns, SHORT rows)
705 {
706 - try
707 - {
708 - wsl::shared::retry::RetryWithTimeout<void>(
709 - [&]() {
710 - const std::string data = session.GetStdoutData();
711 - THROW_HR_IF(E_ABORT, data.find(std::format("{} {}\r\n", rows, columns)) == std::string::npos);
712 - },
713 - std::chrono::milliseconds(200),
714 - std::chrono::seconds(60));
715 - }
716 - catch (...)
717 - {
718 - const std::string data = session.GetStdoutData();
719 - VERIFY_FAIL(std::format(
720 - L"Timed out waiting for tty resize. Captured pseudoconsole output: \"{}\"",
721 - wsl::shared::string::MultiByteToWide(EscapeString(data)))
722 - .c_str());
723 - }
706 + WaitForPseudoConsoleOutput(session, std::format("{} {}\r\n", rows, columns));
707 }
708
709 } // namespace
710
711 +void WaitForPseudoConsoleOutput(const WSLCInteractiveSession& session, const std::string& expected, std::chrono::seconds timeout)
712 +{
713 + try
714 + {
715 + wsl::shared::retry::RetryWithTimeout<void>(
716 + [&]() {
717 + const std::string data = session.GetStdoutData();
718 + THROW_HR_IF(E_ABORT, data.find(expected) == std::string::npos);
719 + },
720 + std::chrono::milliseconds(200),
721 + timeout);
722 + }
723 + catch (...)
724 + {
725 + const std::string data = session.GetStdoutData();
726 + VERIFY_FAIL(std::format(
727 + L"Timed out waiting for \"{}\". Captured pseudoconsole output: \"{}\"",
728 + wsl::shared::string::MultiByteToWide(EscapeString(expected)),
729 + wsl::shared::string::MultiByteToWide(EscapeString(data)))
730 + .c_str());
731 + }
732 +}
733 +
734 void VerifyPseudoConsoleTtySize(WSLCInteractiveSession& session, SHORT columns, SHORT rows)
735 {
736 constexpr SHORT resizedColumns = 100;
test/windows/wslc/e2e/WSLCE2EHelpers.h
+4
@@ -209,6 +209,10 @@ wil::com_ptr<IWSLCSession> OpenDefaultElevatedSession();
209
210 void VerifyPseudoConsoleTtySize(WSLCInteractiveSession& session, SHORT columns, SHORT rows);
211
212 +// Waits for a substring to appear in the session's pseudo console output.
213 +void WaitForPseudoConsoleOutput(
214 + const WSLCInteractiveSession& session, const std::string& expected, std::chrono::seconds timeout = std::chrono::seconds(60));
215 +
216 // Starts a local registry container using the COM API and returns the running container (holds it
217 // alive) plus the registry address. Host network for plain http, bridge network for tls enabled.
218 std::pair<wsl::windows::common::RunningWSLCContainer, std::string> StartLocalRegistry(
test/windows/wslc/e2e/WSLCE2EImageBuildTests.cpp
+24 -10
@@ -17,6 +17,7 @@ Abstract:
17 #include "WSLCE2EHelpers.h"
18
19 namespace WSLCE2ETests {
20 +using namespace wsl::shared;
21
22 class WSLCE2EImageBuildTests
23 {
@@ -41,20 +42,28 @@ class WSLCE2EImageBuildTests
42 // safety net for images left behind by a crashed run.
43 static constexpr auto c_builtImagePrefix = L"wslc-e2e-build-";
44
45 + // Port for the local registry backing the --pull test; distinct from the other test classes.
46 + static constexpr USHORT c_registryPort = 15005;
47 +
48 // Returns an RAII guard that best-effort deletes the given image when it goes out of scope. It is
49 // deliberately non-throwing (no VERIFY) because it may run while the stack unwinds after a test
50 // failure; the class-level prune is the authoritative cleanup.
47 - static auto DeleteImageOnExit(const TestImage& image)
51 + static auto DeleteImageOnExit(std::wstring imageNameAndTag)
52 {
49 - return wil::scope_exit([image]() {
53 + return wil::scope_exit([imageNameAndTag = std::move(imageNameAndTag)]() {
54 try
55 {
52 - RunWslc(std::format(L"image delete --force {}", image.NameAndTag()));
56 + RunWslc(std::format(L"image delete --force {}", imageNameAndTag));
57 }
58 CATCH_LOG()
59 });
60 }
61
62 + static auto DeleteImageOnExit(const TestImage& image)
63 + {
64 + return DeleteImageOnExit(image.NameAndTag());
65 + }
66 +
67 // All secret tests build from this single shared (empty) context directory. Each distinct mounted
68 // directory consumes a virtiofs share slot while it is mounted, so reusing a single context path
69 // helps keep secret tests from exhausting the per-session share budget.
@@ -163,7 +172,14 @@ class WSLCE2EImageBuildTests
172
173 WSLC_TEST_METHOD(WSLCE2E_Image_Build_Pull_Success)
174 {
166 - SKIP_TEST_UNSTABLE(); // TODO: Enable when a private image source is available.
175 + // A local registry acts as the private image source that --pull re-resolves the base image from.
176 + auto session = OpenDefaultElevatedSession();
177 + auto [registryContainer, registryAddress] = StartLocalRegistry(*session, "", "", c_registryPort);
178 +
179 + auto registryImage = TagImageForRegistry(DebianTestImage().NameAndTag(), string::MultiByteToWide(registryAddress));
180 + auto registryImageCleanup = DeleteImageOnExit(registryImage);
181 +
182 + RunWslcAndVerify(std::format(L"push {}", registryImage), {.Stderr = L"", .ExitCode = 0});
183
184 auto imageCleanup = DeleteImageOnExit(BuiltImagePull);
185 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-pull";
@@ -175,17 +191,15 @@ class WSLCE2EImageBuildTests
191 THROW_HR_IF(E_FAIL, ec.value() != 0 || !std::filesystem::exists(contextDir));
192
193 auto dockerfilePath = testRoot / L"Dockerfile";
178 - WriteTestFileContent(dockerfilePath, "FROM debian:latest\nCMD [\"echo\", \"pull-ok\"]\n");
194 + auto dockerfile = std::format("FROM {}\nCMD [\"echo\", \"pull-ok\"]\n", string::WideToMultiByte(registryImage));
195 + WriteTestFileContent(dockerfilePath, dockerfile);
196
180 - // Build with --pull --verbose. When --pull causes docker to resolve the base image
181 - // from the registry, the FROM step includes a @sha256: digest (e.g.
182 - // "FROM docker.io/library/debian:latest@sha256:..."). Build progress goes to stderr.
197 + // The base image is already local, so only --pull can make the FROM step resolve a registry digest.
198 auto buildResult = RunWslc(std::format(
199 L"build \"{}\" -f \"{}\" -t {} --pull --verbose", contextDir.wstring(), dockerfilePath.wstring(), BuiltImagePull.NameAndTag()));
200 buildResult.Verify({.Stdout = L"", .ExitCode = 0});
201
187 - VERIFY_IS_TRUE(buildResult.Stderr.has_value());
188 - VERIFY_IS_TRUE(buildResult.Stderr->find(L"@sha256:") != std::wstring::npos);
202 + VERIFY_IS_TRUE(buildResult.StderrContainsSubstring(std::format(L"{}@sha256:", registryImage)));
203 }
204
205 WSLC_TEST_METHOD(WSLCE2E_Image_Build_Target_Success)
test/windows/wslc/e2e/WSLCE2EImageImportTests.cpp
+10 -2
@@ -140,8 +140,16 @@ class WSLCE2EImageImportTests
140
141 WSLC_TEST_METHOD(WSLCE2E_Image_Import_FromStdin_Success)
142 {
143 - // TODO: http://task.ms/62246732
144 - SKIP_TEST_NOT_IMPL();
143 + // Save image as a tarball
144 + auto saveResult = RunWslc(std::format(L"image save --output \"{}\" {}", SavedArchivePath.wstring(), DebianImage.NameAndTag()));
145 + saveResult.Verify({.Stdout = L"", .Stderr = L"", .ExitCode = 0});
146 +
147 + // '-' reads the archive from stdin; a file handle is required because import needs the size.
148 + auto importResult = RunWslcWithStdinFile(std::format(L"image import - {}", ImportedImage.NameAndTag()), SavedArchivePath);
149 + importResult.Verify({.Stderr = L"", .ExitCode = 0});
150 +
151 + VerifyIdOutput(importResult.GetStdoutOneLine(), true);
152 + VerifyImageIsListed(ImportedImage);
153 }
154
155 WSLC_TEST_METHOD(WSLCE2E_Image_Import_InvalidPath)
test/windows/wslc/e2e/WSLCE2EImageSaveTests.cpp
+5 -7
@@ -105,14 +105,12 @@ class WSLCE2EImageSaveTests
105
106 WSLC_TEST_METHOD(WSLCE2E_Image_Save_ToTerminal_Fail)
107 {
108 - // TODO: Re-enable once the test is stable in console-less pipeline environments.
109 - // Opening CONOUT$ may fail when the process has no console attached.
110 - SKIP_TEST_UNSTABLE();
108 + // A pseudo console gives wslc a console stdout, which image save must reject.
109 + const auto commandLine = std::format(L"image save {}", DebianImage.NameAndTag());
110 + auto session = RunWslcInteractive(commandLine, ElevationType::Elevated, PseudoConsole{200, 50});
111
112 - const auto result = RunWslcAndRedirectToFile(std::format(L"image save {}", DebianImage.NameAndTag()));
113 - result.Verify({.Stdout = L"", .ExitCode = 1});
114 - VERIFY_IS_TRUE(result.StderrContainsSubstring(
115 - L"Cannot write image to terminal. Use the -o flag or redirect stdout.\r\nError code: E_INVALIDARG"));
112 + WaitForPseudoConsoleOutput(session, string::WideToMultiByte(Localization::WSLCCLI_ImageSaveStdoutIsTerminalError()));
113 + VERIFY_ARE_EQUAL(1, session.Wait());
114 }
115
116 WSLC_TEST_METHOD(WSLCE2E_Image_Save_ToStdout_Load)