Skip to content
Open
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
1 change: 1 addition & 0 deletions src/windows/wslc/commands/ContainerCreateCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ std::vector<Argument> ContainerCreateCommand::GetArguments() const
Argument::Create(ArgType::User),
Argument::Create(ArgType::Volume, false, NO_LIMIT),
// Argument::Create(ArgType::Virtual),
Argument::Create(ArgType::WorkDir),
};
// clang-format on
}
Expand Down
1 change: 1 addition & 0 deletions src/windows/wslc/commands/ContainerRunCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ std::vector<Argument> ContainerRunCommand::GetArguments() const
Argument::Create(ArgType::User),
Argument::Create(ArgType::Volume, false, NO_LIMIT),
// Argument::Create(ArgType::Virtual),
Argument::Create(ArgType::WorkDir),
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The PR title/description indicates this change is about removing redundant short-alias E2E tests, but this diff introduces new product behavior (wiring up ArgType::WorkDir for container run) plus additional tests/help expectations. Please update the PR title/description to match the scope (adding --workdir/-w support), or split the behavior change into its own PR to keep the stated intent accurate.

Copilot uses AI. Check for mistakes.
};
// clang-format on
}
Expand Down
5 changes: 5 additions & 0 deletions src/windows/wslc/services/ContainerService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& sessio
containerLauncher.SetUser(std::move(user));
}

if (!options.WorkingDirectory.empty())
{
containerLauncher.SetWorkingDirectory(std::string(options.WorkingDirectory));
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The std::string(options.WorkingDirectory) construction is redundant if options.WorkingDirectory is already a std::string (it forces an extra copy). Prefer passing options.WorkingDirectory directly, or std::move(options.WorkingDirectory) if ownership transfer is intended and options won’t use it afterward.

Suggested change
containerLauncher.SetWorkingDirectory(std::string(options.WorkingDirectory));
containerLauncher.SetWorkingDirectory(options.WorkingDirectory);

Copilot uses AI. Check for mistakes.
}

for (const auto& tmpfsSpec : options.Tmpfs)
{
auto tmpfsMount = TmpfsMount::Parse(tmpfsSpec);
Expand Down
10 changes: 10 additions & 0 deletions test/windows/wslc/CommandLineTestCases.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ COMMAND_LINE_TEST_CASE(L"container start --attach cont", L"start", true)
COMMAND_LINE_TEST_CASE(L"container start -a cont", L"start", true)
COMMAND_LINE_TEST_CASE(L"create ubuntu:latest", L"create", true)
COMMAND_LINE_TEST_CASE(L"container create --name foo ubuntu", L"create", true)
COMMAND_LINE_TEST_CASE(L"create --workdir /app ubuntu", L"create", true)
COMMAND_LINE_TEST_CASE(L"create -w /app ubuntu", L"create", true)
COMMAND_LINE_TEST_CASE(L"container create --workdir /app ubuntu sh", L"create", true)
COMMAND_LINE_TEST_CASE(L"create --workdir", L"create", false) // Missing value for --workdir
COMMAND_LINE_TEST_CASE(L"create --workdir \"\" ubuntu", L"create", false) // Empty working directory
COMMAND_LINE_TEST_CASE(L"run --workdir /app ubuntu echo hello", L"run", true)
COMMAND_LINE_TEST_CASE(L"run -w /app ubuntu echo hello", L"run", true)
COMMAND_LINE_TEST_CASE(L"container run --workdir /app ubuntu sh", L"run", true)
COMMAND_LINE_TEST_CASE(L"run --workdir", L"run", false) // Missing value for --workdir
COMMAND_LINE_TEST_CASE(L"run --workdir \"\" ubuntu echo hello", L"run", false) // Empty working directory
COMMAND_LINE_TEST_CASE(L"exec cont1 echo Hello", L"exec", true)
COMMAND_LINE_TEST_CASE(L"exec cont1", L"exec", false) // Missing required command argument
COMMAND_LINE_TEST_CASE(L"container exec -it cont1 sh -c \"echo a && echo b\"", L"exec", true) // docker exec example
Expand Down
90 changes: 90 additions & 0 deletions test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,96 @@ class WSLCCLIExecutionUnitTests
VERIFY_ARE_EQUAL(std::string("/app"), options.WorkingDirectory);
}

// Test: Full parse of 'run --workdir "" image cmd' rejects empty working directory
TEST_METHOD(RunCommand_ParseWorkDirEmptyValue_ThrowsArgumentException)
{
auto invocation = CreateInvocationFromCommandLine(L"wslc --workdir \"\" ubuntu sh");
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

These tests are named/commented as parsing run ... and create ..., but the command lines passed to CreateInvocationFromCommandLine don’t include the run/create subcommand. This can cause the tests to exercise a different parsing path (or pass/fail for the wrong reason). Update the test command lines to include the explicit subcommand, e.g. wslc run --workdir ... and wslc create --workdir ... (and the -w variants) so the tests accurately cover the intended commands.

Copilot uses AI. Check for mistakes.

ContainerRunCommand command{L""};
CLIExecutionContext context;
command.ParseArguments(invocation, context.Args);

VERIFY_THROWS_SPECIFIC(
command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto&) { return true; });
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The exception predicate always returns true, so these tests will pass for any ArgumentException (including ones thrown for unrelated validation failures). Make the predicate validate something specific to the --workdir empty-value case (e.g., the parameter name/flag in the message or an error code field, if available) so the test can’t succeed for the wrong reason.

Suggested change
command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto&) { return true; });
command.ValidateArguments(context.Args),
wsl::windows::wslc::ArgumentException,
[](const auto& ex)
{
const std::string message = ex.what();
const bool mentionsWorkDir =
message.find("--workdir") != std::string::npos ||
message.find("workdir") != std::string::npos ||
message.find("-w") != std::string::npos;
const bool mentionsEmptyOrInvalidValue =
message.find("empty") != std::string::npos ||
message.find("invalid") != std::string::npos ||
message.find("value") != std::string::npos;
return mentionsWorkDir && mentionsEmptyOrInvalidValue;
});

Copilot uses AI. Check for mistakes.
}

// Test: Full parse of 'run --workdir /path image cmd' sets WorkingDirectory
TEST_METHOD(RunCommand_ParseWorkDirLongOption_SetsWorkingDirectory)
{
auto invocation = CreateInvocationFromCommandLine(L"wslc --workdir /tmp/mydir ubuntu sh");
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

These tests are named/commented as parsing run ... and create ..., but the command lines passed to CreateInvocationFromCommandLine don’t include the run/create subcommand. This can cause the tests to exercise a different parsing path (or pass/fail for the wrong reason). Update the test command lines to include the explicit subcommand, e.g. wslc run --workdir ... and wslc create --workdir ... (and the -w variants) so the tests accurately cover the intended commands.

Copilot uses AI. Check for mistakes.

ContainerRunCommand command{L""};
CLIExecutionContext context;
command.ParseArguments(invocation, context.Args);
command.ValidateArguments(context.Args);

wsl::windows::wslc::task::SetContainerOptionsFromArgs(context);

const auto& options = context.Data.Get<Data::ContainerOptions>();
VERIFY_ARE_EQUAL(std::string("/tmp/mydir"), options.WorkingDirectory);
}

// Test: Full parse of 'run -w /path image cmd' (short alias) sets WorkingDirectory
TEST_METHOD(RunCommand_ParseWorkDirShortOption_SetsWorkingDirectory)
{
auto invocation = CreateInvocationFromCommandLine(L"wslc -w /app ubuntu sh");
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

These tests are named/commented as parsing run ... and create ..., but the command lines passed to CreateInvocationFromCommandLine don’t include the run/create subcommand. This can cause the tests to exercise a different parsing path (or pass/fail for the wrong reason). Update the test command lines to include the explicit subcommand, e.g. wslc run --workdir ... and wslc create --workdir ... (and the -w variants) so the tests accurately cover the intended commands.

Copilot uses AI. Check for mistakes.

ContainerRunCommand command{L""};
CLIExecutionContext context;
command.ParseArguments(invocation, context.Args);
command.ValidateArguments(context.Args);

wsl::windows::wslc::task::SetContainerOptionsFromArgs(context);

const auto& options = context.Data.Get<Data::ContainerOptions>();
VERIFY_ARE_EQUAL(std::string("/app"), options.WorkingDirectory);
}

// Test: Full parse of 'create --workdir "" image cmd' rejects empty working directory
TEST_METHOD(CreateCommand_ParseWorkDirEmptyValue_ThrowsArgumentException)
{
auto invocation = CreateInvocationFromCommandLine(L"wslc --workdir \"\" ubuntu sh");
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

These tests are named/commented as parsing run ... and create ..., but the command lines passed to CreateInvocationFromCommandLine don’t include the run/create subcommand. This can cause the tests to exercise a different parsing path (or pass/fail for the wrong reason). Update the test command lines to include the explicit subcommand, e.g. wslc run --workdir ... and wslc create --workdir ... (and the -w variants) so the tests accurately cover the intended commands.

Copilot uses AI. Check for mistakes.

ContainerCreateCommand command{L""};
CLIExecutionContext context;
command.ParseArguments(invocation, context.Args);

VERIFY_THROWS_SPECIFIC(
command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto&) { return true; });
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The exception predicate always returns true, so these tests will pass for any ArgumentException (including ones thrown for unrelated validation failures). Make the predicate validate something specific to the --workdir empty-value case (e.g., the parameter name/flag in the message or an error code field, if available) so the test can’t succeed for the wrong reason.

Suggested change
command.ValidateArguments(context.Args), wsl::windows::wslc::ArgumentException, [](const auto&) { return true; });
command.ValidateArguments(context.Args),
wsl::windows::wslc::ArgumentException,
[](const auto& ex)
{
const std::string message = ex.what();
return message.find("workdir") != std::string::npos;
});

Copilot uses AI. Check for mistakes.
}

// Test: Full parse of 'create --workdir /path image cmd' sets WorkingDirectory
TEST_METHOD(CreateCommand_ParseWorkDirLongOption_SetsWorkingDirectory)
{
auto invocation = CreateInvocationFromCommandLine(L"wslc --workdir /tmp/mydir ubuntu sh");
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

These tests are named/commented as parsing run ... and create ..., but the command lines passed to CreateInvocationFromCommandLine don’t include the run/create subcommand. This can cause the tests to exercise a different parsing path (or pass/fail for the wrong reason). Update the test command lines to include the explicit subcommand, e.g. wslc run --workdir ... and wslc create --workdir ... (and the -w variants) so the tests accurately cover the intended commands.

Copilot uses AI. Check for mistakes.

ContainerCreateCommand command{L""};
CLIExecutionContext context;
command.ParseArguments(invocation, context.Args);
command.ValidateArguments(context.Args);

wsl::windows::wslc::task::SetContainerOptionsFromArgs(context);

const auto& options = context.Data.Get<Data::ContainerOptions>();
VERIFY_ARE_EQUAL(std::string("/tmp/mydir"), options.WorkingDirectory);
}

// Test: Full parse of 'create -w /path image cmd' (short alias) sets WorkingDirectory
TEST_METHOD(CreateCommand_ParseWorkDirShortOption_SetsWorkingDirectory)
{
auto invocation = CreateInvocationFromCommandLine(L"wslc -w /app ubuntu sh");
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

These tests are named/commented as parsing run ... and create ..., but the command lines passed to CreateInvocationFromCommandLine don’t include the run/create subcommand. This can cause the tests to exercise a different parsing path (or pass/fail for the wrong reason). Update the test command lines to include the explicit subcommand, e.g. wslc run --workdir ... and wslc create --workdir ... (and the -w variants) so the tests accurately cover the intended commands.

Copilot uses AI. Check for mistakes.

ContainerCreateCommand command{L""};
CLIExecutionContext context;
command.ParseArguments(invocation, context.Args);
command.ValidateArguments(context.Args);

wsl::windows::wslc::task::SetContainerOptionsFromArgs(context);

const auto& options = context.Data.Get<Data::ContainerOptions>();
VERIFY_ARE_EQUAL(std::string("/app"), options.WorkingDirectory);
}

// Test: Command Line test parsing all cases defined in CommandLineTestCases.h
// This test verifies the command line parsing logic used by the CLI and executes the same
// code as the CLI up to the point of command execution, including parsing and argument validtion.
Expand Down
11 changes: 11 additions & 0 deletions test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,16 @@ class WSLCE2EContainerCreateTests
result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1});
}

WSLC_TEST_METHOD(WSLCE2E_Container_Create_WorkDir)
{
auto result =
RunWslc(std::format(L"container create --name {} --workdir /tmp {} pwd", WslcContainerName, DebianImage.NameAndTag()));
result.Verify({.Stderr = L"", .ExitCode = 0});

result = RunWslc(std::format(L"container start -a {}", WslcContainerName));
result.Verify({.Stdout = L"/tmp\n", .Stderr = L"", .ExitCode = 0});
}

private:
// Test container name
const std::wstring WslcContainerName = L"wslc-test-container";
Expand Down Expand Up @@ -603,6 +613,7 @@ class WSLCE2EContainerCreateTests
<< L" -t,--tty Open a TTY with the container process.\r\n"
<< L" -u,--user User ID for the process (name|uid|uid:gid)\r\n"
<< L" -v,--volume Bind mount a volume to the container\r\n"
<< L" -w,--workdir Working directory inside the container\r\n"
<< L" -h,--help Shows help about the selected command\r\n"
<< L"\r\n";
return options.str();
Expand Down
7 changes: 7 additions & 0 deletions test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,12 @@ class WSLCE2EContainerRunTests
result.Verify({.Stderr = L"invalid mount path: '' mount path must be absolute\r\nError code: E_FAIL\r\n", .ExitCode = 1});
}

WSLC_TEST_METHOD(WSLCE2E_Container_Run_WorkDir)
{
auto result = RunWslc(std::format(L"container run --rm --workdir /tmp {} pwd", DebianImage.NameAndTag()));
result.Verify({.Stdout = L"/tmp\n", .Stderr = L"", .ExitCode = 0});
}

private:
// Test container name
const std::wstring WslcContainerName = L"wslc-test-container";
Expand Down Expand Up @@ -627,6 +633,7 @@ class WSLCE2EContainerRunTests
<< L" -t,--tty Open a TTY with the container process.\r\n"
<< L" -u,--user User ID for the process (name|uid|uid:gid)\r\n"
<< L" -v,--volume Bind mount a volume to the container\r\n"
<< L" -w,--workdir Working directory inside the container\r\n"
<< L" -h,--help Shows help about the selected command\r\n"
<< L"\r\n";
return options.str();
Expand Down