Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/shared/inc/CommandLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ class ArgumentParser

ArgumentParser(const std::wstring& CommandLine, LPCWSTR Name, int StartIndex = 1, bool ignoreUnknownArgs = false) :
m_parseIndex(StartIndex), m_name(Name), m_ignoreUnknownArgs(ignoreUnknownArgs)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these new lines intentional?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not intentional. I started editing this section but ended up not needing the change, and I missed removing the extra newline. I’ll clean it up.

{
m_argv.reset(CommandLineToArgvW(std::wstring(CommandLine).c_str(), &m_argc));
THROW_LAST_ERROR_IF(!m_argv);
Expand All @@ -318,6 +319,7 @@ class ArgumentParser

ArgumentParser(int argc, const char* const* argv, bool ignoreUnknownArgs = false) :
m_argc(argc), m_argv(argv), m_parseIndex(1), m_ignoreUnknownArgs(ignoreUnknownArgs)

{
}

Expand Down
1 change: 1 addition & 0 deletions src/windows/wsladiag/wsladiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ static int ReportError(const std::wstring& context, HRESULT hr)
}

// Handler for `wsladiag shell <SessionName>` command.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Seems like something is inserting extra newlines?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not intentional. I started editing this section but ended up not needing the change, and I missed removing the extra newline. I’ll clean it up.

static int RunShellCommand(std::wstring_view commandLine)
{
std::wstring sessionName;
Expand Down
3 changes: 2 additions & 1 deletion test/windows/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ set(SOURCES
PluginTests.cpp
PolicyTests.cpp
InstallerTests.cpp
WSLATests.cpp)
WSLATests.cpp
WsladiagTests.cpp)

set(HEADERS
Common.h
Expand Down
2 changes: 1 addition & 1 deletion test/windows/WSLATests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class WSLATests
if (result.Code != expectedResult)
{
LogError(
"Comman didn't return expected code (%i). ExitCode: %i, Stdout: '%hs', Stderr: '%hs'",
"Command didn't return expected code (%i). ExitCode: %i, Stdout: '%hs', Stderr: '%hs'",
expectedResult,
result.Code,
result.Output[1].c_str(),
Expand Down
184 changes: 184 additions & 0 deletions test/windows/WsladiagTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

WsladiagTests.cpp

Abstract:

This file contains smoke tests for wsladiag.

--*/

#include "precomp.h"
#include "Common.h"
#include "Localization.h"

#include <format>
#include <filesystem>

namespace WsladiagTests {
class WsladiagTests
{
WSL_TEST_CLASS(WsladiagTests)
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test class uses WSL_TEST_CLASS macro which doesn't include wsladiag.exe in the BinaryUnderTest property list. While wslaservice.exe is included (which wsladiag depends on), the test is directly executing wsladiag.exe and should declare it as a binary under test. Consider adding a TEST_CLASS_PROPERTY for wsladiag.exe after the WSL_TEST_CLASS macro, similar to how other test classes declare their specific binaries.

Suggested change
WSL_TEST_CLASS(WsladiagTests)
WSL_TEST_CLASS(WsladiagTests)
TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"wsladiag.exe");

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OneBlue does BinaryUnderTest actually do something useful in WSL? Most docs I see on it seem related to internal infra.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've always assumed that TE would watch said binaries in case they crash and fail the test case if that happens, but I'm not actually not sure that it is the case

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beena352 for completeness, we should specify TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"wsladiag.exe") here.


// Add CRLFs for exact-string comparisons
static std::wstring AddCrlf(const std::wstring& input)
{
std::wstring messageWithCrlf;

for (const auto ch : input)
{
if (ch == L'\n')
{
messageWithCrlf += L'\r';
}
messageWithCrlf += ch;
}

return messageWithCrlf;
}

static std::wstring GetUsageText()
{
auto usage = AddCrlf(std::wstring(wsl::shared::Localization::MessageWsladiagUsage()));
if (usage.empty() || usage.back() != L'\n')
{
usage += L"\r\n";
}
return usage;
}

static std::wstring BuildWsladiagCmd(const std::wstring& args)
{
const auto installPath = wsl::windows::common::wslutil::GetMsiPackagePath();
VERIFY_IS_TRUE(installPath.has_value());

const auto exePath = std::filesystem::path(*installPath) / L"wsladiag.exe";
const auto exe = exePath.wstring();

return args.empty() ? std::format(L"\"{}\"", exe) : std::format(L"\"{}\" {}", exe, args);
}
// Execute wsladiag with given arguments and return output, error, and exit code
static std::tuple<std::wstring, std::wstring, int> RunWsladiag(const std::wstring& args)
{
std::wstring commandLine = BuildWsladiagCmd(args);
return LxsstuLaunchCommandAndCaptureOutputWithResult(commandLine.data());
}

static void ValidateWsladiagOutput(const std::wstring& args, int expectedExitCode, const std::wstring& expectedStdout, const std::wstring& expectedStderr)
{
auto [stdOut, stdErr, exitCode] = RunWsladiag(args);
VERIFY_ARE_EQUAL(expectedExitCode, exitCode);
VERIFY_ARE_EQUAL(expectedStdout, stdOut);
VERIFY_ARE_EQUAL(expectedStderr, stdErr);
}

// Test that wsladiag list command "no sessions" message
TEST_METHOD(List_NoSessions)
{
auto [stdOut, stdErr, exitCode] = RunWsladiag(L"list");

VERIFY_ARE_EQUAL(0, exitCode);
VERIFY_ARE_EQUAL(L"", stdErr);
VERIFY_ARE_EQUAL(std::wstring(wsl::shared::Localization::MessageWslaNoSessionsFound()) + L"\r\n", stdOut);
Comment on lines +84 to +86
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use ValidateWsladiagOutput here as well?

}

TEST_METHOD(List_ShowsSessions)
{
auto [stdOut, stdErr, exitCode] = RunWsladiag(L"list");

VERIFY_ARE_EQUAL(0, exitCode);
VERIFY_ARE_EQUAL(L"", stdErr);

const std::wstring noSessions = std::wstring(wsl::shared::Localization::MessageWslaNoSessionsFound()) + L"\r\n";

if (stdOut == noSessions)
{
return;
}

VERIFY_IS_TRUE(stdOut.find(L"ID") != std::wstring::npos);
VERIFY_IS_TRUE(stdOut.find(L"Creator PID") != std::wstring::npos);
VERIFY_IS_TRUE(stdOut.find(L"Display Name") != std::wstring::npos);
}

// Test that wsladiag --help shows usage information
TEST_METHOD(Help_ShowsUsage)
{
ValidateWsladiagOutput(L"--help", 0, L"", GetUsageText());
}

// Test that -h shows usage information
TEST_METHOD(Help_ShortFlag_ShowsUsage)
{
ValidateWsladiagOutput(L"-h", 0, L"", GetUsageText());
}

// Test that wsladiag with no arguments shows usage information
TEST_METHOD(EmptyCommand_ShowsUsage)
{
ValidateWsladiagOutput(L"", 0, L"", GetUsageText());
}

// Test that unknown commands show error message and usage
TEST_METHOD(UnknownCommand_ShowsError)
{
const std::wstring verb = L"blah";
const std::wstring errorMsg = std::wstring(wsl::shared::Localization::MessageWslaUnknownCommand(verb.c_str()));
const std::wstring usage = GetUsageText();

auto [stdOut, stdErr, exitCode] = RunWsladiag(verb);

VERIFY_ARE_EQUAL(1, exitCode);
VERIFY_ARE_EQUAL(L"", stdOut);

const std::wstring expected = errorMsg + L"\r\n" + usage;
VERIFY_ARE_EQUAL(expected, stdErr);
}

// Test that shell command without session name shows error
TEST_METHOD(Shell_MissingName_ShowsError)
{
auto [stdOut, stdErr, exitCode] = RunWsladiag(L"shell");

VERIFY_ARE_EQUAL(1, exitCode);
VERIFY_ARE_EQUAL(L"", stdOut);

const std::wstring errorLine = L"Command line argument <SessionName> requires a value.";
const std::wstring helpHint = L"Please use 'wsladiag shell --help' to get a list of supported arguments.";
const std::wstring errorCode = L"Error code: E_INVALIDARG";

const std::wstring expected = errorLine + L"\r\n" + helpHint + L"\r\n" + errorCode + L"\r\n";
VERIFY_ARE_EQUAL(expected, stdErr);
}

// Test shell command with invalid session name (non verbose mode)
TEST_METHOD(Shell_InvalidSessionName_NonVerbose)
{
const std::wstring name = L"DefinitelyNotARealSession";
auto [stdOut, stdErr, exitCode] = RunWsladiag(L"shell " + name);

VERIFY_ARE_EQUAL(1, exitCode);
VERIFY_ARE_EQUAL(L"", stdOut);

const auto expected = std::wstring(wsl::shared::Localization::MessageWslaSessionNotFound(name.c_str()));
VERIFY_IS_TRUE(stdErr.find(expected) != std::wstring::npos);
}

// Test shell command with invalid session name (verbose mode)
TEST_METHOD(Shell_InvalidSessionName_Verbose)
{
const std::wstring name = L"DefinitelyNotARealSession";
auto [stdOut, stdErr, exitCode] = RunWsladiag(std::format(L"shell {} --verbose", name));

VERIFY_ARE_EQUAL(1, exitCode);
VERIFY_ARE_EQUAL(L"", stdOut);

const auto expected = std::wstring(wsl::shared::Localization::MessageWslaSessionNotFound(name.c_str()));
VERIFY_IS_TRUE(stdErr.find(expected) != std::wstring::npos);
}
};
} // namespace WsladiagTests