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)