Skip to content

CLI: TableOutput unit tests#40095

Draft
dkbennett wants to merge 2 commits intofeature/wsl-for-appsfrom
user/dkbennett/tabletests
Draft

CLI: TableOutput unit tests#40095
dkbennett wants to merge 2 commits intofeature/wsl-for-appsfrom
user/dkbennett/tabletests

Conversation

@dkbennett
Copy link
Copy Markdown
Member

Summary of the Pull Request

Updates the table class slightly to make it easier for unit testing.
Adds unit tests to verify table output behavior.

PR Checklist

  • Closes: Link to issue #xxx
  • Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
  • Tests: Added/updated if needed and all pass
  • Localization: All end user facing strings can be localized
  • Dev docs: Added/updated if needed
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Main change to tableoutput is adding a default output function and the ability to override it. The override is mainly for testing, but it may be useful in other contexts in the future.

Validation Steps Performed

Tests validated and passed

Copilot AI review requested due to automatic review settings April 3, 2026 16:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR makes TableOutput more unit-testable by adding injectable output and a console-width override, and introduces new CLI-focused unit tests to validate table rendering behavior.

Changes:

  • Add an overridable output function to TableOutput (defaulting to stdout) to enable in-memory capture in tests.
  • Add a console width override to make column-shrinking tests deterministic.
  • Add a new unit test suite covering headers, padding, width expansion/truncation, shrinking, and IsEmpty().

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
test/windows/wslc/WSLCCLITableOutputUnitTests.cpp Adds unit tests for TableOutput rendering and sizing behavior using the new output injection hooks.
src/windows/wslc/core/TableOutput.h Adds SetOutputFunction and SetConsoleWidthOverride, and routes rendered lines through the configured output function.

Comment on lines +7 to +13
WSLCTableOutputUnitTests.cpp

Abstract:

Unit tests for the TableOutput class.
Output is redirected to an in-memory std::wstringstream so that tests
are fully self-contained and do not depend on a real console or FILE*.
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The file header comment is inconsistent with the actual file name (Module Name says WSLCTableOutputUnitTests.cpp, but the file is WSLCCLITableOutputUnitTests.cpp) and the Abstract mentions redirecting output to a std::wstringstream, while the tests capture output via SetOutputFunction into a vector<wstring>. Please update the header comment to match the current test implementation to avoid confusion.

Suggested change
WSLCTableOutputUnitTests.cpp
Abstract:
Unit tests for the TableOutput class.
Output is redirected to an in-memory std::wstringstream so that tests
are fully self-contained and do not depend on a real console or FILE*.
WSLCCLITableOutputUnitTests.cpp
Abstract:
Unit tests for the TableOutput class.
Output is captured via SetOutputFunction into an in-memory
std::vector<std::wstring> so that tests are fully self-contained and do
not depend on a real console or FILE*.

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +140
// Use a custom padding of 3 (the default) and verify the data row
// contains at least 3 spaces between the first column value and the
// start of the second column value.
TableOutputCapture<2> cap(TableOutput<2>::header_t{L"NAME", L"STATUS"}, /*sizingBuffer=*/50, /*columnPadding=*/3);

cap.table.OutputLine({L"abc", L"ok"});
cap.table.Complete();

// Data row: "abc" padded to header width ("NAME"=4) + 3 spaces, then "ok"
// Expected: "abc ok" with appropriate spacing
const std::wstring& dataLine = cap.lines[1];
VERIFY_IS_TRUE(dataLine.find(L"abc") != std::wstring::npos);
VERIFY_IS_TRUE(dataLine.find(L"ok") != std::wstring::npos);

// There must be at least 3 spaces between the two values
auto namePos = dataLine.find(L"abc");
auto statusPos = dataLine.find(L"ok");
VERIFY_IS_TRUE(statusPos > namePos + 3);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This spacing assertion is too weak: statusPos > namePos + 3 can pass even when there are fewer than 3 spaces between the values (e.g., if ok starts immediately after abc plus 1 space). Consider asserting statusPos >= namePos + wcslen(L"abc") + columnPadding (or directly checking that the substring between the tokens contains the expected number of spaces).

Suggested change
// Use a custom padding of 3 (the default) and verify the data row
// contains at least 3 spaces between the first column value and the
// start of the second column value.
TableOutputCapture<2> cap(TableOutput<2>::header_t{L"NAME", L"STATUS"}, /*sizingBuffer=*/50, /*columnPadding=*/3);
cap.table.OutputLine({L"abc", L"ok"});
cap.table.Complete();
// Data row: "abc" padded to header width ("NAME"=4) + 3 spaces, then "ok"
// Expected: "abc ok" with appropriate spacing
const std::wstring& dataLine = cap.lines[1];
VERIFY_IS_TRUE(dataLine.find(L"abc") != std::wstring::npos);
VERIFY_IS_TRUE(dataLine.find(L"ok") != std::wstring::npos);
// There must be at least 3 spaces between the two values
auto namePos = dataLine.find(L"abc");
auto statusPos = dataLine.find(L"ok");
VERIFY_IS_TRUE(statusPos > namePos + 3);
constexpr size_t columnPadding = 3;
constexpr wchar_t firstValue[] = L"abc";
// Use a custom padding of 3 (the default) and verify the data row
// contains at least 3 spaces between the first column value and the
// start of the second column value.
TableOutputCapture<2> cap(TableOutput<2>::header_t{L"NAME", L"STATUS"}, /*sizingBuffer=*/50, /*columnPadding=*/columnPadding);
cap.table.OutputLine({firstValue, L"ok"});
cap.table.Complete();
// Data row: "abc" padded to header width ("NAME"=4) + 3 spaces, then "ok"
// Expected: "abc ok" with appropriate spacing
const std::wstring& dataLine = cap.lines[1];
VERIFY_IS_TRUE(dataLine.find(firstValue) != std::wstring::npos);
VERIFY_IS_TRUE(dataLine.find(L"ok") != std::wstring::npos);
// There must be at least 3 spaces between the end of the first value
// and the start of the second value.
auto namePos = dataLine.find(firstValue);
auto statusPos = dataLine.find(L"ok");
constexpr size_t firstValueLength = (sizeof(firstValue) / sizeof(firstValue[0])) - 1;
VERIFY_IS_TRUE(statusPos >= namePos + firstValueLength + columnPadding);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

nice, copilot very good at this sort of thing

const std::wstring& dataLine = cap.lines[1];
auto posX = dataLine.find(L'x');
auto posY = dataLine.find(L'y');
VERIFY_IS_TRUE(posY >= posX + customPadding);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The position calculation is off by one: to enforce 5-space padding after the x character, the start of y should be compared against posX + 1 + customPadding (the code currently uses posX + customPadding). Also, please assert posX != npos and posY != npos before doing arithmetic/comparisons; otherwise npos can wrap on addition and produce false positives.

Suggested change
VERIFY_IS_TRUE(posY >= posX + customPadding);
VERIFY_IS_TRUE(posX != std::wstring::npos);
VERIFY_IS_TRUE(posY != std::wstring::npos);
VERIFY_IS_TRUE(posY >= posX + 1 + customPadding);

Copilot uses AI. Check for mistakes.
// Data line: "abc" starts at position 0, "ok" must not start before
// position 14 + padding.
const std::wstring& dataLine = cap.lines[1];
auto posOk = dataLine.find(L"ok");
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

posOk is not validated before being compared. If find returns npos, the comparison can still pass (since npos is a very large size_t). Add VERIFY_IS_TRUE(posOk != std::wstring::npos) (or equivalent) before the range check to avoid false positives.

Suggested change
auto posOk = dataLine.find(L"ok");
auto posOk = dataLine.find(L"ok");
VERIFY_IS_TRUE(posOk != std::wstring::npos);

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +149
// Override the output function (e.g. redirect to a stringstream in tests).
void SetOutputFunction(OutputFn fn)
{
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

SetOutputFunction allows callers to pass an empty std::function (e.g. OutputFn{}), but OutputLineToStream unconditionally invokes m_outputFn(rendered), which will throw std::bad_function_call. Consider guarding against empty callables (reset to the default output if !fn), and/or providing a public way to restore the default output function (e.g. ResetOutputFunction() or making DefaultOutputFn() publicly accessible).

Suggested change
// Override the output function (e.g. redirect to a stringstream in tests).
void SetOutputFunction(OutputFn fn)
{
// Override the output function (e.g. redirect to a stringstream in tests).
// Ignore empty callables so the current/default output function remains valid.
void SetOutputFunction(OutputFn fn)
{
if (!fn)
{
return;
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'll add an assert for this, we do not expect this to be used extensively or really at all outside tests at this time.

// Helper: capture all lines emitted by a TableOutput into a vector<wstring>.
// ---------------------------------------------------------------------------
template <size_t N>
struct TableOutputCapture
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should we move this to a shared/helper class? I think this will be helpful for E2E testing as well. Today we find image and container names in output lines. With this class, we can reconstruct the table and instead do VERIFY_ARE_EQUAL(rendered, stdout).

Alternatively, maybe the TableOutput class can have a ToString() that would give us the output as a string

@benhillis
Copy link
Copy Markdown
Member

Hey @dkbennett 👋 — Following up on this draft PR. CI is green, but there are 6 unresolved review threads that still need to be addressed:

  • File header mismatch
  • Spacing assertion too weak
  • Off-by-one in position calculation
  • Missing \
    pos\ guards
  • Empty function guard in \SetOutputFunction\
  • Suggestion to move to a shared helper

Is this change still being worked on? It's been a few days since the last update. Let us know if you need any help with the review feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants