| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | WSLCE2EWarningTests.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | End-to-end tests validating how warnings emitted by the WSLC COM service are |
| 12 | surfaced (or intentionally suppressed) on the wslc.exe CLI's stderr via the |
| 13 | IWarningCallback integration. |
| 14 | --*/ |
| 15 | |
| 16 | #include "precomp.h" |
| 17 | #include "windows/Common.h" |
| 18 | #include "WSLCExecutor.h" |
| 19 | #include "WSLCE2EHelpers.h" |
| 20 | #include "TestImageRegistry.h" |
| 21 | #include <WSLCProcessLauncher.h> |
| 22 | |
| 23 | namespace WSLCE2ETests { |
| 24 | using namespace wsl::shared; |
| 25 | |
| 26 | class WSLCE2EWarningTests |
| 27 | { |
| 28 | WSLC_TEST_CLASS(WSLCE2EWarningTests) |
| 29 | |
| 30 | const TestImage AlpineImage = AlpineTestImage(); |
| 31 | WSADATA m_wsaData{}; |
| 32 | |
| 33 | TEST_CLASS_SETUP(ClassSetup) |
| 34 | { |
| 35 | THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &m_wsaData)); |
| 36 | TestImageRegistry::Instance().EnsureLoaded(AlpineImage); |
| 37 | return true; |
| 38 | } |
| 39 | |
| 40 | TEST_CLASS_CLEANUP(ClassCleanup) |
| 41 | { |
| 42 | WSACleanup(); |
| 43 | return true; |
| 44 | } |
| 45 | |
| 46 | static std::string RunDockerInSession(IWSLCSession& session, std::vector<std::string>&& args) |
| 47 | { |
| 48 | wsl::windows::common::WSLCProcessLauncher launcher("/usr/bin/docker", args); |
| 49 | auto result = launcher.Launch(session).WaitAndCaptureOutput(); |
| 50 | VERIFY_ARE_EQUAL(0, result.Code); |
| 51 | |
| 52 | // Trim trailing whitespace from the captured stdout (fd 1). |
| 53 | auto output = result.Output[1]; |
| 54 | output.erase(output.find_last_not_of(" \n\r") + 1); |
| 55 | return output; |
| 56 | } |
| 57 | |
| 58 | // Best-effort removal of a container from docker. |
| 59 | static void RemoveContainerNoThrow(const std::string& containerId) |
| 60 | try |
| 61 | { |
| 62 | wil::com_ptr<IWSLCSessionManager> sessionManager; |
| 63 | THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&sessionManager))); |
| 64 | wsl::windows::common::security::ConfigureForCOMImpersonation(sessionManager.get()); |
| 65 | |
| 66 | wil::com_ptr<IWSLCSession> session; |
| 67 | THROW_IF_FAILED(sessionManager->OpenSessionByName(nullptr, &session)); |
| 68 | wsl::windows::common::security::ConfigureForCOMImpersonation(session.get()); |
| 69 | |
| 70 | wsl::windows::common::WSLCProcessLauncher launcher("/usr/bin/docker", {"/usr/bin/docker", "rm", "-f", containerId}); |
| 71 | launcher.Launch(*session).WaitAndCaptureOutput(); |
| 72 | } |
| 73 | CATCH_LOG() |
| 74 | |
| 75 | // Injects a container with corrupt WSLC metadata into the default session's storage, |
| 76 | // then verifies that running the wslc.exe CLI does not surface the COM service's recovery |
| 77 | // warning on stderr: recovery runs outside the user's current command, so it is logged |
| 78 | // (and written to the event log) rather than streamed back via IWarningCallback. |
| 79 | WSLC_TEST_METHOD(WSLCE2E_Warning_ContainerRecoveryNotPrintedOnStderr) |
| 80 | { |
| 81 | std::string corruptContainerId; |
| 82 | |
| 83 | // Inject a container whose WSLC metadata label is not valid JSON. RecoverExistingContainers |
| 84 | // will fail to parse it the next time the session is created. |
| 85 | { |
| 86 | auto session = OpenDefaultElevatedSession(); |
| 87 | corruptContainerId = RunDockerInSession( |
| 88 | *session, |
| 89 | {"/usr/bin/docker", |
| 90 | "create", |
| 91 | "--label", |
| 92 | "wslc.container.metadata=INVALID_JSON", |
| 93 | string::WideToMultiByte(AlpineImage.NameAndTag())}); |
| 94 | VERIFY_IS_FALSE(corruptContainerId.empty()); |
| 95 | } |
| 96 | |
| 97 | // cleanup: remove the corrupt container |
| 98 | auto cleanup = wil::scope_exit([&]() { RemoveContainerNoThrow(corruptContainerId); }); |
| 99 | |
| 100 | // Terminate the default session so the next wslc command recreates it and runs recovery. |
| 101 | EnsureSessionIsTerminated(); |
| 102 | |
| 103 | // Run the CLI: recovery of the corrupt container fails, but because the recovery runs |
| 104 | // outside the user's current command, the warning is not printed on stderr. |
| 105 | auto result = RunWslc(L"container list"); |
| 106 | VERIFY_IS_TRUE(result.ExitCode.has_value()); |
| 107 | VERIFY_ARE_EQUAL(0u, result.ExitCode.value()); |
| 108 | |
| 109 | const auto recoveryWarning = |
| 110 | wsl::shared::Localization::MessageWslcFailedToRecoverContainer(string::MultiByteToWide(corruptContainerId)); |
| 111 | |
| 112 | // Assert that capture actually worked before searching it, matching the other e2e tests: |
| 113 | // RunWslc always populates Stderr, so a missing value means capture broke and the search |
| 114 | // below would pass without proving anything. |
| 115 | VERIFY_IS_TRUE(result.Stderr.has_value()); |
| 116 | VERIFY_IS_TRUE(result.Stderr->find(recoveryWarning) == std::wstring::npos); |
| 117 | } |
| 118 | }; |
| 119 | |
| 120 | } // namespace WSLCE2ETests |