diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw
index 6eb41abef..387b37493 100644
--- a/localization/strings/en-US/Resources.resw
+++ b/localization/strings/en-US/Resources.resw
@@ -2125,6 +2125,10 @@ Usage:
Container '{}' is not running.
{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
+
+ Container '{}' was created with --rm and cannot be restarted.
+ {FixedPlaceholder="{}"}{Locked="--rm "}Command line arguments, file names and string inserts should not be translated
+
Container '{}' is running.
{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated
@@ -2515,6 +2519,12 @@ For privacy information about this product please visit https://aka.ms/privacy.<
Removes containers.
+
+ Restart containers.
+
+
+ Restarts one or more containers. Running containers are stopped and started again; stopped containers are started.
+
Run a container.
diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl
index 7639c4517..f57cdde4d 100644
--- a/src/windows/service/inc/wslc.idl
+++ b/src/windows/service/inc/wslc.idl
@@ -492,6 +492,7 @@ interface IWSLCContainer : IUnknown
HRESULT Stats([out] LPSTR* Output);
HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options);
HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName);
+ HRESULT Restart([in] WSLCSignal Signal, [in] LONG TimeoutSeconds, [in, unique] IWarningCallback* WarningCallback);
}
typedef struct _WSLCDeletedImageInformation
diff --git a/src/windows/wslc/commands/ContainerCommand.cpp b/src/windows/wslc/commands/ContainerCommand.cpp
index 9d5002f3f..c8ce8070e 100644
--- a/src/windows/wslc/commands/ContainerCommand.cpp
+++ b/src/windows/wslc/commands/ContainerCommand.cpp
@@ -32,6 +32,7 @@ std::vector> ContainerCommand::GetCommands() const
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
+ commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
commands.push_back(std::make_unique(FullName()));
diff --git a/src/windows/wslc/commands/ContainerCommand.h b/src/windows/wslc/commands/ContainerCommand.h
index 74f6c5629..2fe1ccca8 100644
--- a/src/windows/wslc/commands/ContainerCommand.h
+++ b/src/windows/wslc/commands/ContainerCommand.h
@@ -168,6 +168,21 @@ struct ContainerRemoveCommand final : public Command
void ExecuteInternal(CLIExecutionContext& context) const override;
};
+// Restart Command
+struct ContainerRestartCommand final : public Command
+{
+ constexpr static std::wstring_view CommandName = L"restart";
+ ContainerRestartCommand(const std::wstring& parent) : Command(CommandName, parent)
+ {
+ }
+ std::vector GetArguments() const override;
+ std::wstring ShortDescription() const override;
+ std::wstring LongDescription() const override;
+
+protected:
+ void ExecuteInternal(CLIExecutionContext& context) const override;
+};
+
// Run Command
struct ContainerRunCommand final : public Command
{
diff --git a/src/windows/wslc/commands/ContainerRestartCommand.cpp b/src/windows/wslc/commands/ContainerRestartCommand.cpp
new file mode 100644
index 000000000..199a7ed46
--- /dev/null
+++ b/src/windows/wslc/commands/ContainerRestartCommand.cpp
@@ -0,0 +1,54 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ ContainerRestartCommand.cpp
+
+Abstract:
+
+ Implementation of command execution logic.
+
+--*/
+
+#include "ContainerCommand.h"
+#include "CLIExecutionContext.h"
+#include "ContainerTasks.h"
+#include "SessionTasks.h"
+#include "Task.h"
+
+using namespace wsl::windows::wslc::execution;
+using namespace wsl::windows::wslc::task;
+using namespace wsl::shared;
+
+namespace wsl::windows::wslc {
+// Container Restart Command
+std::vector ContainerRestartCommand::GetArguments() const
+{
+ return {
+ Argument::Create(ArgType::ContainerId, std::nullopt, NO_LIMIT),
+ Argument::Create(ArgType::Signal),
+ Argument::Create(ArgType::Time),
+ };
+}
+
+std::wstring ContainerRestartCommand::ShortDescription() const
+{
+ return Localization::WSLCCLI_ContainerRestartDesc();
+}
+
+std::wstring ContainerRestartCommand::LongDescription() const
+{
+ return Localization::WSLCCLI_ContainerRestartLongDesc();
+}
+
+// clang-format off
+void ContainerRestartCommand::ExecuteInternal(CLIExecutionContext& context) const
+{
+ context
+ << ResolveSession
+ << RestartContainers;
+}
+// clang-format on
+} // namespace wsl::windows::wslc
diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp
index 9a85ebf98..6fe332785 100644
--- a/src/windows/wslc/services/ContainerService.cpp
+++ b/src/windows/wslc/services/ContainerService.cpp
@@ -492,6 +492,14 @@ void ContainerService::Stop(Session& session, const std::string& id, StopContain
THROW_IF_FAILED_EXCEPT(container->Stop(options.Signal, options.Timeout), WSLC_E_CONTAINER_NOT_RUNNING);
}
+void ContainerService::Restart(Session& session, const std::string& id, StopContainerOptions options)
+{
+ wil::com_ptr container;
+ THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
+ auto warningCallback = Microsoft::WRL::Make();
+ THROW_IF_FAILED(container->Restart(options.Signal, options.Timeout, warningCallback.Get()));
+}
+
void ContainerService::Kill(Session& session, const std::string& id, WSLCSignal signal)
{
wil::com_ptr container;
diff --git a/src/windows/wslc/services/ContainerService.h b/src/windows/wslc/services/ContainerService.h
index fccdf2056..1bbfe4926 100644
--- a/src/windows/wslc/services/ContainerService.h
+++ b/src/windows/wslc/services/ContainerService.h
@@ -28,6 +28,7 @@ struct ContainerService
static models::CreateContainerResult Create(models::Session& session, const std::string& image, models::ContainerOptions options);
static int Start(models::Session& session, const std::string& id, bool attach = false);
static void Stop(models::Session& session, const std::string& id, models::StopContainerOptions options);
+ static void Restart(models::Session& session, const std::string& id, models::StopContainerOptions options);
static void Kill(models::Session& session, const std::string& id, WSLCSignal signal = WSLCSignalSIGKILL);
static void Delete(models::Session& session, const std::string& id, bool force);
static std::vector List(
diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp
index ad7d112d8..027f77af9 100644
--- a/src/windows/wslc/tasks/ContainerTasks.cpp
+++ b/src/windows/wslc/tasks/ContainerTasks.cpp
@@ -767,6 +767,29 @@ void StopContainers(CLIExecutionContext& context)
}
}
+void RestartContainers(CLIExecutionContext& context)
+{
+ WI_ASSERT(context.Data.Contains(Data::Session));
+ auto& session = context.Data.Get();
+ auto containersToRestart = context.Args.GetAll();
+ StopContainerOptions options;
+ if (context.Args.Contains(ArgType::Signal))
+ {
+ options.Signal = validation::GetWSLCSignalFromString(context.Args.Get());
+ }
+
+ if (context.Args.Contains(ArgType::Time))
+ {
+ options.Timeout = validation::GetIntegerFromString(context.Args.Get());
+ }
+
+ for (const auto& id : containersToRestart)
+ {
+ ContainerService::Restart(session, WideToMultiByte(id), options);
+ PrintMessage(id);
+ }
+}
+
void ViewContainerLogs(CLIExecutionContext& context)
{
WI_ASSERT(context.Data.Contains(Data::Session));
diff --git a/src/windows/wslc/tasks/ContainerTasks.h b/src/windows/wslc/tasks/ContainerTasks.h
index 7055f5e37..458fc946f 100644
--- a/src/windows/wslc/tasks/ContainerTasks.h
+++ b/src/windows/wslc/tasks/ContainerTasks.h
@@ -39,6 +39,7 @@ void KillContainers(CLIExecutionContext& context);
void ListContainers(CLIExecutionContext& context);
void PruneContainers(CLIExecutionContext& context);
void RemoveContainers(CLIExecutionContext& context);
+void RestartContainers(CLIExecutionContext& context);
void RunContainer(CLIExecutionContext& context);
void SetContainerOptionsFromArgs(CLIExecutionContext& context);
void ShowContainerStats(CLIExecutionContext& context);
diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp
index 674b0b0cd..3be3d00fe 100644
--- a/src/windows/wslcsession/WSLCContainer.cpp
+++ b/src/windows/wslcsession/WSLCContainer.cpp
@@ -2524,6 +2524,40 @@ void WSLCContainerImpl::DisconnectFromNetwork(LPCSTR NetworkName)
TraceLoggingValue(NetworkName, "NetworkName"));
}
+void WSLCContainerImpl::Restart(WSLCSignal Signal, LONG TimeoutSeconds)
+{
+ // N.B. Snapshot under a brief shared lock and release before composing Stop/Start.
+ // m_lock is a non-recursive srwlock; Stop and Start each take it exclusively.
+ WSLCContainerState snapshotState{};
+ WSLCContainerFlags snapshotFlags{};
+ {
+ auto lock = m_lock.lock_shared();
+ snapshotState = m_state;
+ snapshotFlags = m_containerFlags;
+ }
+
+ // Refuse --rm before stopping: Stop -> OnStopped would delete the container.
+ THROW_HR_WITH_USER_ERROR_IF(
+ HRESULT_FROM_WIN32(ERROR_INVALID_STATE),
+ Localization::MessageWslcContainerRestartAutoRemove(m_id),
+ WI_IsFlagSet(snapshotFlags, WSLCContainerFlagsRm));
+
+ THROW_HR_IF_MSG(
+ HRESULT_FROM_WIN32(ERROR_INVALID_STATE),
+ snapshotState != WslcContainerStateRunning && snapshotState != WslcContainerStateExited && snapshotState != WslcContainerStateCreated,
+ "Cannot restart container '%hs', state %i",
+ m_id.c_str(),
+ snapshotState);
+
+ if (snapshotState == WslcContainerStateRunning)
+ {
+ // N.B. If the container exits on its own before Stop acquires m_lock, Stop no-ops on Exited.
+ Stop(Signal, TimeoutSeconds, /*Kill*/ false);
+ }
+
+ Start(WSLCContainerStartFlagsNone, nullptr);
+}
+
HRESULT WSLCContainer::GetLabels(WSLCLabelInformation** Labels, ULONG* Count)
try
{
@@ -2553,6 +2587,15 @@ try
}
CATCH_RETURN();
+HRESULT WSLCContainer::Restart(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, _In_opt_ IWarningCallback* WarningCallback)
+try
+{
+ // N.B. WarningCallback is ambient here so EMIT_USER_WARNING reaches the client from both the Stop and Start phases.
+ WSLCExecutionContext context(&m_session, WarningCallback);
+ return CallImpl(&WSLCContainerImpl::Restart, Signal, TimeoutSeconds);
+}
+CATCH_RETURN();
+
HRESULT WSLCContainer::InterfaceSupportsErrorInfo(REFIID riid)
{
return riid == __uuidof(IWSLCContainer) || riid == __uuidof(IWSLCCompatContainer) ? S_OK : S_FALSE;
diff --git a/src/windows/wslcsession/WSLCContainer.h b/src/windows/wslcsession/WSLCContainer.h
index 10fd57fd3..df2632c08 100644
--- a/src/windows/wslcsession/WSLCContainer.h
+++ b/src/windows/wslcsession/WSLCContainer.h
@@ -110,6 +110,7 @@ class WSLCContainerImpl
void GetLabels(WSLCLabelInformation** Labels, ULONG* Count) const;
void ConnectToNetwork(const WSLCNetworkConnectionOptions* Options);
void DisconnectFromNetwork(LPCSTR NetworkName);
+ void Restart(WSLCSignal Signal, LONG TimeoutSeconds);
void CopyTo(IWSLCContainer** Container) const;
@@ -247,6 +248,7 @@ class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLCContainer
IFACEMETHOD(Stats)(_Out_ LPSTR* Output) override;
IFACEMETHOD(ConnectToNetwork)(_In_ const WSLCNetworkConnectionOptions* Options) override;
IFACEMETHOD(DisconnectFromNetwork)(_In_ LPCSTR NetworkName) override;
+ IFACEMETHOD(Restart)(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, _In_opt_ IWarningCallback* WarningCallback) override;
// IWSLCCompatContainer.
IFACEMETHOD(Start)(_In_ WSLCContainerStartFlags Flags) override;
diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp
index 80881d63e..272bd02de 100644
--- a/test/windows/WSLCTests.cpp
+++ b/test/windows/WSLCTests.cpp
@@ -6192,6 +6192,63 @@ class WSLCTests
}
}
+ WSLC_TEST_METHOD(ContainerRestart)
+ {
+ // Restart a running container.
+ {
+ WSLCContainerLauncher launcher("debian:latest", "test-restart-running", {"sleep", "99999"});
+ auto container = launcher.Launch(*m_defaultSession);
+ VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
+
+ VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr));
+ VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
+ }
+
+ // Restart of an exited container just starts it.
+ {
+ WSLCContainerLauncher launcher("debian:latest", "test-restart-exited", {"sleep", "99999"});
+ auto container = launcher.Launch(*m_defaultSession);
+ VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0));
+ VERIFY_ARE_EQUAL(container.State(), WslcContainerStateExited);
+
+ VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr));
+ VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
+ }
+
+ // Restart of a created (never-started) container starts it.
+ {
+ WSLCContainerLauncher launcher("debian:latest", "test-restart-created", {"sleep", "99999"});
+ auto container = launcher.Create(*m_defaultSession);
+ VERIFY_ARE_EQUAL(container.State(), WslcContainerStateCreated);
+
+ VERIFY_SUCCEEDED(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr));
+ VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
+ }
+
+ // --rm containers are refused; Stop is not invoked so the container stays Running.
+ {
+ WSLCContainerLauncher launcher("debian:latest", "test-restart-autorm", {"sleep", "99999"});
+ launcher.SetContainerFlags(WSLCContainerFlagsRm);
+ auto container = launcher.Launch(*m_defaultSession);
+ VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
+
+ auto id = container.Id();
+ VERIFY_ARE_EQUAL(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
+ ValidateCOMErrorMessage(std::format(L"Container '{}' was created with --rm and cannot be restarted.", id));
+ VERIFY_ARE_EQUAL(container.State(), WslcContainerStateRunning);
+ }
+
+ // Deleted containers can't be restarted.
+ {
+ WSLCContainerLauncher launcher("debian:latest", "test-restart-deleted", {"sleep", "99999"});
+ auto container = launcher.Launch(*m_defaultSession);
+ VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalSIGKILL, 0));
+ VERIFY_SUCCEEDED(container.Get().Delete(WSLCDeleteFlagsNone));
+
+ VERIFY_ARE_EQUAL(container.Get().Restart(WSLCSignalSIGKILL, 0, nullptr), RPC_E_DISCONNECTED);
+ }
+ }
+
WSLC_TEST_METHOD(OpenContainer)
{
auto expectOpen = [&](const char* Id, HRESULT expectedResult = S_OK) {
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRestartTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRestartTests.cpp
new file mode 100644
index 000000000..20a647158
--- /dev/null
+++ b/test/windows/wslc/e2e/WSLCE2EContainerRestartTests.cpp
@@ -0,0 +1,224 @@
+/*++
+
+Copyright (c) Microsoft. All rights reserved.
+
+Module Name:
+
+ WSLCE2EContainerRestartTests.cpp
+
+Abstract:
+
+ This file contains end-to-end tests for WSLC.
+--*/
+
+#include "precomp.h"
+#include "windows/Common.h"
+#include "WSLCExecutor.h"
+#include "WSLCE2EHelpers.h"
+
+namespace WSLCE2ETests {
+using namespace wsl::shared;
+
+class WSLCE2EContainerRestartTests
+{
+ WSLC_TEST_CLASS(WSLCE2EContainerRestartTests)
+
+ TEST_CLASS_SETUP(ClassSetup)
+ {
+ EnsureImageIsLoaded(DebianImage);
+ return true;
+ }
+
+ TEST_CLASS_CLEANUP(ClassCleanup)
+ {
+ EnsureContainerDoesNotExist(WslcContainerName);
+ EnsureContainerDoesNotExist(WslcContainerName2);
+ EnsureImageIsDeleted(DebianImage);
+ return true;
+ }
+
+ TEST_METHOD_SETUP(TestMethodSetup)
+ {
+ EnsureContainerDoesNotExist(WslcContainerName);
+ EnsureContainerDoesNotExist(WslcContainerName2);
+ return true;
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_HelpCommand)
+ {
+ auto result = RunWslc(L"container restart --help");
+ result.Verify({.Stdout = GetHelpMessage(), .Stderr = L"", .ExitCode = 0});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_RunningContainer)
+ {
+ auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ const auto containerId = result.GetStdoutOneLine();
+ VERIFY_IS_FALSE(containerId.empty());
+
+ VerifyContainerIsListed(containerId, L"running");
+
+ result = RunWslc(std::format(L"container restart {} -t 0", containerId));
+ result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0});
+
+ // Should be running again after restart.
+ VerifyContainerIsListed(containerId, L"running");
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_StoppedContainer)
+ {
+ auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ const auto containerId = result.GetStdoutOneLine();
+ VERIFY_IS_FALSE(containerId.empty());
+
+ result = RunWslc(std::format(L"container stop {} -t 0", containerId));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ VerifyContainerIsListed(containerId, L"exited");
+
+ // Restart of a stopped container should just start it.
+ result = RunWslc(std::format(L"container restart {} -t 0", containerId));
+ result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0});
+ VerifyContainerIsListed(containerId, L"running");
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_ByName)
+ {
+ auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ const auto containerId = result.GetStdoutOneLine();
+ VERIFY_IS_FALSE(containerId.empty());
+
+ VerifyContainerIsListed(containerId, L"running");
+
+ result = RunWslc(std::format(L"container restart {} -t 0", WslcContainerName));
+ result.Verify({.Stdout = std::format(L"{}\r\n", WslcContainerName), .Stderr = L"", .ExitCode = 0});
+
+ VerifyContainerIsListed(containerId, L"running");
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_NotFound)
+ {
+ VerifyContainerIsNotListed(WslcContainerName);
+
+ auto result = RunWslc(std::format(L"container restart {} -t 0", WslcContainerName));
+ result.Verify(
+ {.Stderr = std::format(L"Container '{}' not found.\r\nError code: WSLC_E_CONTAINER_NOT_FOUND\r\n", WslcContainerName),
+ .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_TargetedContainerOnly)
+ {
+ auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ const auto firstContainerId = result.GetStdoutOneLine();
+ VERIFY_IS_FALSE(firstContainerId.empty());
+
+ result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName2, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ const auto secondContainerId = result.GetStdoutOneLine();
+ VERIFY_IS_FALSE(secondContainerId.empty());
+
+ VerifyContainerIsListed(firstContainerId, L"running");
+ VerifyContainerIsListed(secondContainerId, L"running");
+
+ // Restart only the first container.
+ result = RunWslc(std::format(L"container restart {} -t 0", firstContainerId));
+ result.Verify({.Stdout = std::format(L"{}\r\n", firstContainerId), .Stderr = L"", .ExitCode = 0});
+
+ VerifyContainerIsListed(firstContainerId, L"running");
+ VerifyContainerIsListed(secondContainerId, L"running");
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_AutoRemoveRefused)
+ {
+ // --rm containers cannot be restarted (Stop would delete them).
+ auto result =
+ RunWslc(std::format(L"container run -d --rm --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ const auto containerId = result.GetStdoutOneLine();
+ VERIFY_IS_FALSE(containerId.empty());
+
+ VerifyContainerIsListed(containerId, L"running");
+
+ result = RunWslc(std::format(L"container restart {} -t 0", containerId));
+ const auto expectedStderr =
+ std::format(L"Container '{}' was created with --rm and cannot be restarted.\r\nError code: ERROR_INVALID_STATE\r\n", containerId);
+ result.Verify({.Stderr = expectedStderr, .ExitCode = 1});
+
+ // Container should still be running after the refusal.
+ VerifyContainerIsListed(containerId, L"running");
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_InvalidSignal)
+ {
+ auto result = RunWslc(std::format(L"container run --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+
+ result = RunWslc(std::format(L"container restart {} -s 0 -t 0", WslcContainerName));
+ result.Verify({.Stderr = L"Invalid signal value: 0 is out of valid range (1-31).\r\n", .ExitCode = 1});
+
+ result = RunWslc(std::format(L"container restart {} -s 32 -t 0", WslcContainerName));
+ result.Verify({.Stderr = L"Invalid signal value: 32 is out of valid range (1-31).\r\n", .ExitCode = 1});
+ }
+
+ WSLC_TEST_METHOD(WSLCE2E_Container_Restart_InvalidTimeout)
+ {
+ auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
+ result.Verify({.Stderr = L"", .ExitCode = 0});
+ const auto containerId = result.GetStdoutOneLine();
+ VERIFY_IS_FALSE(containerId.empty());
+
+ result = RunWslc(std::format(L"container restart {} -t abc", containerId));
+ result.Verify({.Stderr = L"Invalid time argument value: abc\r\n", .ExitCode = 1});
+
+ // Container is unaffected by the parse failure.
+ VerifyContainerIsListed(containerId, L"running");
+ }
+
+private:
+ const std::wstring WslcContainerName = L"wslc-test-container";
+ const std::wstring WslcContainerName2 = L"wslc-test-container-2";
+ const TestImage& DebianImage = DebianTestImage();
+
+ std::wstring GetHelpMessage() const
+ {
+ std::wstringstream output;
+ output << GetWslcHeader() //
+ << GetDescription() //
+ << GetUsage() //
+ << GetAvailableCommands() //
+ << GetAvailableOptions();
+ return output.str();
+ }
+
+ std::wstring GetDescription() const
+ {
+ return Localization::WSLCCLI_ContainerRestartLongDesc() + L"\r\n\r\n";
+ }
+
+ std::wstring GetUsage() const
+ {
+ return L"Usage: wslc container restart [] []\r\n\r\n";
+ }
+
+ std::wstring GetAvailableCommands() const
+ {
+ std::wstringstream commands;
+ commands << L"The following arguments are available:\r\n" << L" container-id Container ID\r\n" << L"\r\n";
+ return commands.str();
+ }
+
+ std::wstring GetAvailableOptions() const
+ {
+ std::wstringstream options;
+ options << L"The following options are available:\r\n"
+ << L" -s,--signal Signal to send\r\n"
+ << L" -t,--time Time in seconds to wait before executing (default 5)\r\n"
+ << L" -?,--help Shows help about the selected command\r\n"
+ << L"\r\n";
+ return options.str();
+ }
+};
+} // namespace WSLCE2ETests
diff --git a/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp
index 09caf623c..a7e5ca810 100644
--- a/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp
+++ b/test/windows/wslc/e2e/WSLCE2EContainerTests.cpp
@@ -79,6 +79,7 @@ class WSLCE2EContainerTests
{L"list", Localization::WSLCCLI_ContainerListDesc()},
{L"prune", Localization::WSLCCLI_ContainerPruneDesc()},
{L"remove", Localization::WSLCCLI_ContainerRemoveDesc()},
+ {L"restart", Localization::WSLCCLI_ContainerRestartDesc()},
{L"run", Localization::WSLCCLI_ContainerRunDesc()},
{L"start", Localization::WSLCCLI_ContainerStartDesc()},
{L"stats", Localization::WSLCCLI_ContainerStatsDesc()},