Add wslc container restart command (#41435)

beena352 committed Sep 8, 2026 at 14:36 UTC 28d0fed363f0498baa1377768e676ab0f04f1944
20 files changed +485 -31
localization/strings/en-US/Resources.resw
+7 -1
@@ -2714,6 +2714,12 @@ Example: tar -cf - files | wslc container cp - CONTAINER:/path</value>
2714 <data name="WSLCCLI_ContainerRemoveLongDesc" xml:space="preserve">
2715 <value>Removes containers.</value>
2716 </data>
2717 + <data name="WSLCCLI_ContainerRestartDesc" xml:space="preserve">
2718 + <value>Restart containers.</value>
2719 + </data>
2720 + <data name="WSLCCLI_ContainerRestartLongDesc" xml:space="preserve">
2721 + <value>Restarts containers. Containers that are not running are started.</value>
2722 + </data>
2723 <data name="WSLCCLI_ContainerRunDesc" xml:space="preserve">
2724 <value>Run a container.</value>
2725 </data>
@@ -3378,7 +3384,7 @@ On first run, creates the file with all settings commented out at their defaults
3384 <value>New image reference in the image-name[:tag] format</value>
3385 </data>
3386 <data name="WSLCCLI_TimeArgDescription" xml:space="preserve">
3381 - <value>Time in seconds to wait before executing (default 5)</value>
3387 + <value>Seconds to wait before killing the container (default: the container's configured stop timeout)</value>
3388 </data>
3389 <data name="WSLCCLI_TTYArgDescription" xml:space="preserve">
3390 <value>Open a TTY with the container process.</value>
src/windows/wslc/arguments/ArgumentDefinitions.h
+1
@@ -131,6 +131,7 @@ _(Tail, "tail", L"n", Kind::Value,
131 _(Tag, "tag", L"t", Kind::Value, NoConversion, Localization::WSLCCLI_TagArgDescription()) \
132 _(Target, "target", NO_ALIAS, Kind::Positional, NoConversion, Localization::WSLCCLI_TargetArgDescription()) \
133 _(Time, "time", L"t", Kind::Value, LONG, Localization::WSLCCLI_TimeArgDescription()) \
134 +_(Timeout, "timeout", L"t", Kind::Value, LONG, Localization::WSLCCLI_TimeArgDescription()) \
135 _(TMPFS, "tmpfs", NO_ALIAS, Kind::Value, ParsedMount, Localization::WSLCCLI_TMPFSArgDescription()) \
136 _(TTY, "tty", L"t", Kind::Flag, NoConversion, Localization::WSLCCLI_TTYArgDescription()) \
137 _(Type, "type", L"t", Kind::Value, InspectType, Localization::WSLCCLI_TypeArgDescription()) \
src/windows/wslc/arguments/ArgumentValidation.cpp
+6
@@ -188,6 +188,12 @@ void Argument::Validate(ArgMap& execArgs) const
188 });
189 break;
190
191 + case ArgType::Timeout:
192 + CacheConverted<ArgType::Timeout>(execArgs, m_name, [](const std::wstring& value, const std::wstring& name) {
193 + return validation::GetIntegerFromString<LONG>(value, name);
194 + });
195 + break;
196 +
197 case ArgType::Secret:
198 CacheConverted<ArgType::Secret>(
199 execArgs, m_name, [](const std::wstring& value, const std::wstring&) { return validation::ParseSecretSpec(value); });
src/windows/wslc/commands/ContainerCommand.cpp
+1
@@ -33,6 +33,7 @@ std::vector<std::unique_ptr<Command>> ContainerCommand::GetCommands() const
33 commands.push_back(std::make_unique<ContainerListCommand>(FullName()));
34 commands.push_back(std::make_unique<ContainerPruneCommand>(FullName()));
35 commands.push_back(std::make_unique<ContainerRemoveCommand>(FullName()));
36 + commands.push_back(std::make_unique<ContainerRestartCommand>(FullName()));
37 commands.push_back(std::make_unique<ContainerRunCommand>(FullName()));
38 commands.push_back(std::make_unique<ContainerStartCommand>(FullName()));
39 commands.push_back(std::make_unique<ContainerStatsCommand>(FullName()));
src/windows/wslc/commands/ContainerCommand.h
+15
@@ -183,6 +183,21 @@ protected:
183 void ExecuteInternal(CLIExecutionContext& context) const override;
184 };
185
186 +// Restart Command
187 +struct ContainerRestartCommand final : public Command
188 +{
189 + constexpr static std::wstring_view CommandName = L"restart";
190 + ContainerRestartCommand(const std::wstring& parent) : Command(CommandName, parent)
191 + {
192 + }
193 + std::vector<Argument> GetArguments() const override;
194 + std::wstring ShortDescription() const override;
195 + std::wstring LongDescription() const override;
196 +
197 +protected:
198 + void ExecuteInternal(CLIExecutionContext& context) const override;
199 +};
200 +
201 // Run Command
202 struct ContainerRunCommand final : public Command
203 {
src/windows/wslc/commands/ContainerRestartCommand.cpp new
+50
@@ -0,0 +1,50 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + ContainerRestartCommand.cpp
8 +
9 +Abstract:
10 +
11 + Implementation of command execution logic.
12 +
13 +--*/
14 +
15 +#include "ContainerCommand.h"
16 +#include "CLIExecutionContext.h"
17 +#include "ContainerTasks.h"
18 +#include "SessionTasks.h"
19 +#include "Task.h"
20 +
21 +using namespace wsl::windows::wslc::execution;
22 +using namespace wsl::windows::wslc::task;
23 +using namespace wsl::shared;
24 +
25 +namespace wsl::windows::wslc {
26 +// Container Restart Command
27 +std::vector<Argument> ContainerRestartCommand::GetArguments() const
28 +{
29 + return {
30 + Argument::Create(ArgType::ContainerId, {.Required = true, .Limit = Limit::Unlimited}),
31 + Argument::Create(ArgType::Signal, {.Desc = Localization::WSLCCLI_ContainerStopSignalArgDescription()}),
32 + Argument::Create(ArgType::Timeout),
33 + };
34 +}
35 +
36 +std::wstring ContainerRestartCommand::ShortDescription() const
37 +{
38 + return Localization::WSLCCLI_ContainerRestartDesc();
39 +}
40 +
41 +std::wstring ContainerRestartCommand::LongDescription() const
42 +{
43 + return Localization::WSLCCLI_ContainerRestartLongDesc();
44 +}
45 +
46 +void ContainerRestartCommand::ExecuteInternal(CLIExecutionContext& context) const
47 +{
48 + context << ResolveSession << RestartContainers;
49 +}
50 +} // namespace wsl::windows::wslc
src/windows/wslc/commands/RootCommand.cpp
+1
@@ -56,6 +56,7 @@ std::vector<std::unique_ptr<Command>> RootCommand::GetCommands() const
56 commands.push_back(std::make_unique<ImagePullCommand>(FullName()));
57 commands.push_back(std::make_unique<ImagePushCommand>(FullName()));
58 commands.push_back(std::make_unique<ContainerRemoveCommand>(FullName()));
59 + commands.push_back(std::make_unique<ContainerRestartCommand>(FullName()));
60 commands.push_back(std::make_unique<ImageRemoveCommand>(FullName(), true));
61 commands.push_back(std::make_unique<ContainerRunCommand>(FullName()));
62 commands.push_back(std::make_unique<ImageSaveCommand>(FullName()));
src/windows/wslc/core/CLIExecutionContext.cpp
+20
@@ -7,6 +7,9 @@ Copyright (c) Microsoft. All rights reserved.
7 #include "Argument.h"
8 #include "CLIExecutionContext.h"
9
10 +using namespace wsl::shared;
11 +using namespace wsl::windows::common;
12 +
13 namespace wsl::windows::wslc::execution {
14
15 HANDLE CLIExecutionContext::CreateCancelEvent()
@@ -23,4 +26,21 @@ void CLIExecutionContext::ApplyGlobalEnvironmentOptions()
26 Terminal.SetNoColor(GlobalArgs.GetValue<ArgType::NoColor>());
27 }
28
29 +void CLIExecutionContext::ReportError(HRESULT result)
30 +{
31 + std::wstring message;
32 + if (const auto& reported = ReportedError())
33 + {
34 + const auto strings = wslutil::ErrorToString(*reported);
35 + message = strings.Message.empty() ? strings.Code : strings.Message;
36 + }
37 +
38 + Terminal.Error(L"{}\n", Localization::MessageErrorCode(message, wslutil::ErrorCodeToString(result)));
39 +}
40 +
41 +void CLIExecutionContext::ClearError()
42 +{
43 + m_error.reset();
44 +}
45 +
46 } // namespace wsl::windows::wslc::execution
src/windows/wslc/core/CLIExecutionContext.h
+6
@@ -52,6 +52,12 @@ struct CLIExecutionContext : public wsl::windows::common::ExecutionContext
52
53 // Applies and freezes environment-only global options before command-line parsing reports errors.
54 void ApplyGlobalEnvironmentOptions();
55 +
56 + // Prints a caught error to stderr.
57 + void ReportError(HRESULT result);
58 +
59 + // Drops the collected error so a later failure in the same invocation reports its own message.
60 + void ClearError();
61 };
62
63 } // namespace wsl::windows::wslc::execution
src/windows/wslc/core/Main.cpp
+1 -11
@@ -174,17 +174,7 @@ try
174
175 if (FAILED(result))
176 {
177 - if (const auto& reported = context.ReportedError())
178 - {
179 - auto strings = wslutil::ErrorToString(*reported);
180 - auto errorMessage = strings.Message.empty() ? strings.Code : strings.Message;
181 - context.Terminal.Error(L"{}\n", Localization::MessageErrorCode(errorMessage, wslutil::ErrorCodeToString(result)));
182 - }
183 - else
184 - {
185 - // Fallback for errors without context
186 - context.Terminal.Error(L"{}\n", Localization::MessageErrorCode(L"", wslutil::ErrorCodeToString(result)));
187 - }
177 + context.ReportError(result);
178 }
179 }
180
src/windows/wslc/services/ContainerService.cpp
+10
@@ -671,6 +671,16 @@ void ContainerService::Stop(Session& session, const std::string& id, StopContain
671 THROW_IF_FAILED_EXCEPT(container->Stop(options.Signal, options.Timeout), WSLC_E_CONTAINER_NOT_RUNNING);
672 }
673
674 +void ContainerService::Restart(Terminal& terminal, Session& session, const std::string& id, StopContainerOptions options)
675 +{
676 + [[maybe_unused]] auto operation = session.BeginContainerOperation();
677 + wil::com_ptr<IWSLCContainer> container;
678 + THROW_IF_FAILED(session.Get()->OpenContainer(id.c_str(), &container));
679 +
680 + WarningCallback warningCallback(terminal);
681 + THROW_IF_FAILED(container->Restart(options.Signal, options.Timeout, &warningCallback));
682 +}
683 +
684 void ContainerService::Kill(Session& session, const std::string& id, WSLCSignal signal)
685 {
686 [[maybe_unused]] auto operation = session.BeginContainerOperation();
src/windows/wslc/services/ContainerService.h
+1
@@ -54,6 +54,7 @@ struct ContainerService
54 static models::CreateContainerResult Create(Terminal& terminal, models::Session& session, const std::string& image, models::ContainerOptions options);
55 static int Start(Terminal& terminal, models::Session& session, const std::string& id, bool attach = false);
56 static void Stop(models::Session& session, const std::string& id, models::StopContainerOptions options);
57 + static void Restart(Terminal& terminal, models::Session& session, const std::string& id, models::StopContainerOptions options);
58 static void Kill(models::Session& session, const std::string& id, WSLCSignal signal = WSLCSignalSIGKILL);
59 static void Delete(models::Session& session, const std::string& id, bool force, bool deleteVolumes = false);
60 static std::vector<models::ContainerInformation> List(
src/windows/wslc/tasks/ContainerTasks.cpp
+44 -18
@@ -161,6 +161,29 @@ ContainerOutputInformation ToContainerOutput(const ContainerInformation& contain
161
162 namespace wsl::windows::wslc::task {
163
164 +// Every container is attempted even if an earlier one fails; the command still exits nonzero.
165 +template <typename TAction>
166 +static void ForEachContainer(CLIExecutionContext& context, TAction&& action)
167 +{
168 + for (const auto& id : context.Args.GetAllValues<ArgType::ContainerId>())
169 + {
170 + try
171 + {
172 + action(WideToMultiByte(id));
173 + context.Terminal.Output(L"{}\n", id);
174 + }
175 + catch (...)
176 + {
177 + LOG_CAUGHT_EXCEPTION();
178 + context.ReportError(wil::ResultFromCaughtException());
179 +
180 + // CollectErrorImpl keeps the first message when the next container fails with the same HRESULT.
181 + context.ClearError();
182 + context.ExitCode = 1;
183 + }
184 + }
185 +}
186 +
187 static bool TryInspectContainer(
188 Terminal& terminal, Session& session, const std::string& containerId, std::optional<wslc_schema::InspectContainer>& inspectData, bool size = false)
189 {
@@ -268,14 +291,9 @@ void KillContainers(CLIExecutionContext& context)
291 {
292 WI_ASSERT(context.Data.Contains(Data::Session));
293 auto& session = context.Data.Get<Data::Session>();
271 - auto containerIds = context.Args.GetAllValues<ArgType::ContainerId>();
294 const auto signal = context.Args.GetValue<ArgType::Signal>(WSLCSignalSIGKILL);
295
274 - for (const auto& id : containerIds)
275 - {
276 - ContainerService::Kill(session, WideToMultiByte(id), signal);
277 - context.Terminal.Output(L"{}\n", id);
278 - }
296 + ForEachContainer(context, [&](const std::string& id) { ContainerService::Kill(session, id, signal); });
297 }
298
299 void ExportContainer(CLIExecutionContext& context)
@@ -650,14 +668,10 @@ void RemoveContainers(CLIExecutionContext& context)
668 {
669 WI_ASSERT(context.Data.Contains(Data::Session));
670 auto& session = context.Data.Get<Data::Session>();
653 - auto containerIds = context.Args.GetAllValues<ArgType::ContainerId>();
654 - bool force = context.Args.GetValue<ArgType::Force>();
655 - bool deleteVolumes = context.Args.GetValue<ArgType::Volumes>();
656 - for (const auto& id : containerIds)
657 - {
658 - ContainerService::Delete(session, WideToMultiByte(id), force, deleteVolumes);
659 - context.Terminal.Output(L"{}\n", id);
660 - }
671 + const bool force = context.Args.GetValue<ArgType::Force>();
672 + const bool deleteVolumes = context.Args.GetValue<ArgType::Volumes>();
673 +
674 + ForEachContainer(context, [&](const std::string& id) { ContainerService::Delete(session, id, force, deleteVolumes); });
675 }
676
677 void RunContainer(CLIExecutionContext& context)
@@ -1059,7 +1073,6 @@ void StopContainers(CLIExecutionContext& context)
1073 {
1074 WI_ASSERT(context.Data.Contains(Data::Session));
1075 auto& session = context.Data.Get<Data::Session>();
1062 - auto containersToStop = context.Args.GetAllValues<ArgType::ContainerId>();
1076 StopContainerOptions options;
1077
1078 // WSLCSignalNone lets Docker use the container's configured STOPSIGNAL, or its default when none is configured.
@@ -1070,11 +1083,24 @@ void StopContainers(CLIExecutionContext& context)
1083 options.Timeout = context.Args.GetValue<ArgType::Time>();
1084 }
1085
1073 - for (const auto& id : containersToStop)
1086 + ForEachContainer(context, [&](const std::string& id) { ContainerService::Stop(session, id, options); });
1087 +}
1088 +
1089 +void RestartContainers(CLIExecutionContext& context)
1090 +{
1091 + WI_ASSERT(context.Data.Contains(Data::Session));
1092 + auto& session = context.Data.Get<Data::Session>();
1093 + StopContainerOptions options;
1094 +
1095 + // WSLCSignalNone lets Docker use the container's configured STOPSIGNAL, or its default when none is configured.
1096 + options.Signal = context.Args.GetValue<ArgType::Signal>(WSLCSignalNone);
1097 +
1098 + if (context.Args.Contains(ArgType::Timeout))
1099 {
1075 - ContainerService::Stop(context.Data.Get<Data::Session>(), WideToMultiByte(id), options);
1076 - context.Terminal.Output(L"{}\n", id);
1100 + options.Timeout = context.Args.GetValue<ArgType::Timeout>();
1101 }
1102 +
1103 + ForEachContainer(context, [&](const std::string& id) { ContainerService::Restart(context.Terminal, session, id, options); });
1104 }
1105
1106 void ViewContainerLogs(CLIExecutionContext& context)
src/windows/wslc/tasks/ContainerTasks.h
+1
@@ -40,6 +40,7 @@ void KillContainers(CLIExecutionContext& context);
40 void ListContainers(CLIExecutionContext& context);
41 void PruneContainers(CLIExecutionContext& context);
42 void RemoveContainers(CLIExecutionContext& context);
43 +void RestartContainers(CLIExecutionContext& context);
44 void RunContainer(CLIExecutionContext& context);
45 void SetContainerOptionsFromArgs(CLIExecutionContext& context);
46 void ShowContainerStats(CLIExecutionContext& context);
test/windows/wslc/CommandLineTestCases.h
+10
@@ -117,6 +117,16 @@ COMMAND_LINE_TEST_CASE(L"container stop cont1 --signal 9", L"stop", true)
117 COMMAND_LINE_TEST_CASE(L"container stop cont1 --signal SIGALRM", L"stop", true)
118 COMMAND_LINE_TEST_CASE(L"container stop cont1 --signal sigkill", L"stop", true)
119 COMMAND_LINE_TEST_CASE(L"container stop cont1 -s KILL", L"stop", true)
120 +COMMAND_LINE_TEST_CASE(L"container stop cont1 --time 5", L"stop", true)
121 +COMMAND_LINE_TEST_CASE(L"container stop cont1 -t 5", L"stop", true)
122 +COMMAND_LINE_TEST_CASE(L"restart", L"restart", false) // Missing required container-id positional
123 +COMMAND_LINE_TEST_CASE(L"container restart", L"restart", false) // Missing required container-id positional
124 +COMMAND_LINE_TEST_CASE(L"restart cont1", L"restart", true)
125 +COMMAND_LINE_TEST_CASE(L"container restart cont1 cont2", L"restart", true)
126 +COMMAND_LINE_TEST_CASE(L"container restart cont1 --signal SIGTERM", L"restart", true)
127 +COMMAND_LINE_TEST_CASE(L"container restart cont1 -s KILL", L"restart", true)
128 +COMMAND_LINE_TEST_CASE(L"container restart cont1 --timeout 5", L"restart", true)
129 +COMMAND_LINE_TEST_CASE(L"container restart cont1 -t 5", L"restart", true)
130 COMMAND_LINE_TEST_CASE(L"start cont", L"start", true)
131 COMMAND_LINE_TEST_CASE(L"container start cont", L"start", true)
132 COMMAND_LINE_TEST_CASE(L"container start --attach cont", L"start", true)
test/windows/wslc/WSLCCLIArgumentUnitTests.cpp
+2 -1
@@ -412,8 +412,9 @@ class WSLCCLIArgumentUnitTests
412 VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::HealthRetries>(L"3"), 3);
413 VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Last>(L"5"), 5);
414
415 - // string -> LONG
415 + // string -> LONG (Time and Timeout share the converter)
416 VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Time>(L"5"), 5L);
417 + VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Timeout>(L"5"), 5L);
418
419 // string -> ULONGLONG (Tail is a raw integer; Since/Until go through the timestamp parser)
420 VERIFY_ARE_EQUAL(ValidateAndGetCached<ArgType::Tail>(L"10"), 10ULL);
test/windows/wslc/e2e/WSLCE2EContainerKillTests.cpp
+27
@@ -145,9 +145,36 @@ class WSLCE2EContainerKillTests
145 VerifyContainerIsListed(secondContainerId, L"running");
146 }
147
148 + WSLC_TEST_METHOD(WSLCE2E_Container_Kill_ContinuesAfterFailure)
149 + {
150 + // Run first container in background
151 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
152 + result.Verify({.Stderr = L"", .ExitCode = 0});
153 + const auto firstContainerId = result.GetStdoutOneLine();
154 + VERIFY_IS_FALSE(firstContainerId.empty());
155 +
156 + // Run second container in background
157 + result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName2, DebianImage.NameAndTag()));
158 + result.Verify({.Stderr = L"", .ExitCode = 0});
159 + const auto secondContainerId = result.GetStdoutOneLine();
160 + VERIFY_IS_FALSE(secondContainerId.empty());
161 +
162 + // A container that cannot be killed is reported without skipping the ones after it
163 + result = RunWslc(std::format(L"container kill {} {} {}", firstContainerId, InvalidContainerName, secondContainerId));
164 + result.Verify(
165 + {.Stdout = std::format(L"{}\r\n{}\r\n", firstContainerId, secondContainerId),
166 + .Stderr = FormatErrorMessage(
167 + std::format(L"Container '{}' not found.", InvalidContainerName), L"WSLC_E_CONTAINER_NOT_FOUND"),
168 + .ExitCode = 1});
169 +
170 + VerifyContainerIsListed(firstContainerId, L"exited");
171 + VerifyContainerIsListed(secondContainerId, L"exited");
172 + }
173 +
174 private:
175 const std::wstring WslcContainerName = L"wslc-test-container";
176 const std::wstring WslcContainerName2 = L"wslc-test-container-2";
177 + const std::wstring InvalidContainerName = L"wslc-nonexistent-container-for-kill";
178 const TestImage& DebianImage = DebianTestImage();
179 };
180 } // namespace WSLCE2ETests
test/windows/wslc/e2e/WSLCE2EContainerRemoveTests.cpp
+28
@@ -162,6 +162,33 @@ class WSLCE2EContainerRemoveTests
162 VerifyContainerIsNotListed(WslcContainerName2);
163 }
164
165 + WSLC_TEST_METHOD(WSLCE2E_Container_Remove_ContinuesAfterFailure)
166 + {
167 + VerifyContainerIsNotListed(WslcContainerName);
168 + VerifyContainerIsNotListed(WslcContainerName2);
169 +
170 + auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
171 + result.Verify({.Stderr = L"", .ExitCode = 0});
172 + const auto containerId1 = result.GetStdoutOneLine();
173 + VERIFY_IS_FALSE(containerId1.empty());
174 +
175 + result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()));
176 + result.Verify({.Stderr = L"", .ExitCode = 0});
177 + const auto containerId2 = result.GetStdoutOneLine();
178 + VERIFY_IS_FALSE(containerId2.empty());
179 +
180 + // A container that cannot be removed is reported without skipping the ones after it
181 + result = RunWslc(std::format(L"container remove {} {} {}", containerId1, InvalidContainerName, containerId2));
182 + result.Verify(
183 + {.Stdout = std::format(L"{}\r\n{}\r\n", containerId1, containerId2),
184 + .Stderr = FormatErrorMessage(
185 + std::format(L"Container '{}' not found.", InvalidContainerName), L"WSLC_E_CONTAINER_NOT_FOUND"),
186 + .ExitCode = 1});
187 +
188 + VerifyContainerIsNotListed(containerId1);
189 + VerifyContainerIsNotListed(containerId2);
190 + }
191 +
192 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_Volumes_RemovesAnonymousVolume)
193 {
194 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, AnonymousVolumeImage.NameAndTag()));
@@ -264,6 +291,7 @@ private:
291
292 const std::wstring WslcContainerName = L"wslc-test-container";
293 const std::wstring WslcContainerName2 = L"wslc-test-container-2";
294 + const std::wstring InvalidContainerName = L"wslc-nonexistent-container-for-remove";
295 const std::wstring TestVolumeName = L"wslc-e2e-container-remove-volume";
296 const TestImage& DebianImage = DebianTestImage();
297
test/windows/wslc/e2e/WSLCE2EContainerRestartTests.cpp new
+227
@@ -0,0 +1,227 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + WSLCE2EContainerRestartTests.cpp
8 +
9 +Abstract:
10 +
11 + This file contains end-to-end tests for WSLC.
12 +--*/
13 +
14 +#include "precomp.h"
15 +#include "windows/Common.h"
16 +#include "WSLCExecutor.h"
17 +#include "WSLCE2EHelpers.h"
18 +#include "TestImageRegistry.h"
19 +
20 +namespace WSLCE2ETests {
21 +using namespace wsl::shared;
22 +
23 +class WSLCE2EContainerRestartTests
24 +{
25 + WSLC_TEST_CLASS(WSLCE2EContainerRestartTests)
26 +
27 + TEST_CLASS_SETUP(ClassSetup)
28 + {
29 + TestImageRegistry::Instance().EnsureLoaded(DebianImage);
30 + return true;
31 + }
32 +
33 + TEST_CLASS_CLEANUP(ClassCleanup)
34 + {
35 + EnsureContainerDoesNotExist(WslcContainerName);
36 + EnsureContainerDoesNotExist(WslcContainerName2);
37 + return true;
38 + }
39 +
40 + TEST_METHOD_SETUP(TestMethodSetup)
41 + {
42 + EnsureContainerDoesNotExist(WslcContainerName);
43 + EnsureContainerDoesNotExist(WslcContainerName2);
44 + return true;
45 + }
46 +
47 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_HelpCommand)
48 + {
49 + auto result = RunWslc(L"container restart --help");
50 + result.Verify({.Stderr = L"", .ExitCode = 0});
51 + VERIFY_IS_FALSE(result.Stdout.value().empty());
52 + }
53 +
54 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_RunningContainer)
55 + {
56 + // Run a container in the background
57 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
58 + result.Verify({.Stderr = L"", .ExitCode = 0});
59 + const auto containerId = result.GetStdoutOneLine();
60 + VERIFY_IS_FALSE(containerId.empty());
61 +
62 + VerifyContainerIsListed(containerId, L"running");
63 + const auto startedAt = InspectContainer(WslcContainerName).State.StartedAt;
64 +
65 + result = RunWslc(std::format(L"container restart {} -t 0", containerId));
66 + result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0});
67 +
68 + // The container is running again, but from a new init process.
69 + VerifyContainerIsListed(containerId, L"running");
70 + VERIFY_ARE_NOT_EQUAL(startedAt, InspectContainer(WslcContainerName).State.StartedAt);
71 + }
72 +
73 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_ByName)
74 + {
75 + // Run a container in the background
76 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
77 + result.Verify({.Stderr = L"", .ExitCode = 0});
78 + const auto containerId = result.GetStdoutOneLine();
79 + VERIFY_IS_FALSE(containerId.empty());
80 +
81 + VerifyContainerIsListed(containerId, L"running");
82 +
83 + // Restart by container name
84 + result = RunWslc(std::format(L"container restart {} -t 0", WslcContainerName));
85 + result.Verify({.Stdout = std::format(L"{}\r\n", WslcContainerName), .Stderr = L"", .ExitCode = 0});
86 +
87 + VerifyContainerIsListed(containerId, L"running");
88 + }
89 +
90 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_StoppedContainer)
91 + {
92 + // Run a container in the background
93 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
94 + result.Verify({.Stderr = L"", .ExitCode = 0});
95 + const auto containerId = result.GetStdoutOneLine();
96 + VERIFY_IS_FALSE(containerId.empty());
97 +
98 + result = RunWslc(std::format(L"container stop {} -t 0", containerId));
99 + result.Verify({.Stderr = L"", .ExitCode = 0});
100 + VerifyContainerIsListed(containerId, L"exited");
101 +
102 + // Restarting a stopped container starts it
103 + result = RunWslc(std::format(L"container restart {} -t 0", containerId));
104 + result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0});
105 +
106 + VerifyContainerIsListed(containerId, L"running");
107 + }
108 +
109 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_MultipleContainers)
110 + {
111 + // Run first container in background
112 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
113 + result.Verify({.Stderr = L"", .ExitCode = 0});
114 + const auto firstContainerId = result.GetStdoutOneLine();
115 + VERIFY_IS_FALSE(firstContainerId.empty());
116 +
117 + // Run second container in background
118 + result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName2, DebianImage.NameAndTag()));
119 + result.Verify({.Stderr = L"", .ExitCode = 0});
120 + const auto secondContainerId = result.GetStdoutOneLine();
121 + VERIFY_IS_FALSE(secondContainerId.empty());
122 +
123 + result = RunWslc(std::format(L"container restart {} {} -t 0", firstContainerId, secondContainerId));
124 + result.Verify({.Stdout = std::format(L"{}\r\n{}\r\n", firstContainerId, secondContainerId), .Stderr = L"", .ExitCode = 0});
125 +
126 + VerifyContainerIsListed(firstContainerId, L"running");
127 + VerifyContainerIsListed(secondContainerId, L"running");
128 + }
129 +
130 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_ContinuesAfterFailure)
131 + {
132 + // Run first container in background
133 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
134 + result.Verify({.Stderr = L"", .ExitCode = 0});
135 + const auto firstContainerId = result.GetStdoutOneLine();
136 + VERIFY_IS_FALSE(firstContainerId.empty());
137 +
138 + // Run second container in background
139 + result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName2, DebianImage.NameAndTag()));
140 + result.Verify({.Stderr = L"", .ExitCode = 0});
141 + const auto secondContainerId = result.GetStdoutOneLine();
142 + VERIFY_IS_FALSE(secondContainerId.empty());
143 +
144 + // A container that cannot be restarted is reported without skipping the ones after it
145 + result = RunWslc(std::format(L"container restart {} {} {} -t 0", firstContainerId, InvalidContainerName, secondContainerId));
146 + result.Verify(
147 + {.Stdout = std::format(L"{}\r\n{}\r\n", firstContainerId, secondContainerId),
148 + .Stderr = FormatErrorMessage(
149 + std::format(L"Container '{}' not found.", InvalidContainerName), L"WSLC_E_CONTAINER_NOT_FOUND"),
150 + .ExitCode = 1});
151 +
152 + VerifyContainerIsListed(firstContainerId, L"running");
153 + VerifyContainerIsListed(secondContainerId, L"running");
154 + }
155 +
156 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_EachFailureIsReported)
157 + {
158 + VerifyContainerIsNotListed(InvalidContainerName);
159 + VerifyContainerIsNotListed(InvalidContainerName2);
160 +
161 + auto result = RunWslc(std::format(L"container restart {} {} -t 0", InvalidContainerName, InvalidContainerName2));
162 + result.Verify(
163 + {.Stdout = L"",
164 + .Stderr = FormatErrorMessage(
165 + std::format(L"Container '{}' not found.", InvalidContainerName), L"WSLC_E_CONTAINER_NOT_FOUND") +
166 + FormatErrorMessage(
167 + std::format(L"Container '{}' not found.", InvalidContainerName2), L"WSLC_E_CONTAINER_NOT_FOUND"),
168 + .ExitCode = 1});
169 + }
170 +
171 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_NotFound)
172 + {
173 + VerifyContainerIsNotListed(WslcContainerName);
174 +
175 + auto result = RunWslc(std::format(L"container restart {} -t 0", WslcContainerName));
176 + result.Verify(
177 + {.Stderr =
178 + FormatErrorMessage(std::format(L"Container '{}' not found.", WslcContainerName), L"WSLC_E_CONTAINER_NOT_FOUND"),
179 + .ExitCode = 1});
180 + }
181 +
182 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_InvalidSignalName)
183 + {
184 + // Run a container in the background
185 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
186 + result.Verify({.Stderr = L"", .ExitCode = 0});
187 + const auto containerId = result.GetStdoutOneLine();
188 + VERIFY_IS_FALSE(containerId.empty());
189 +
190 + VerifyContainerIsListed(containerId, L"running");
191 +
192 + result = RunWslc(std::format(L"container restart {} -s SIGINVALID -t 0", containerId));
193 + result.Verify({.Stdout = L"", .ExitCode = 1});
194 + VERIFY_IS_TRUE(result.StderrContainsSubstring(
195 + L"Invalid signal value: SIGINVALID is not a recognized signal name or number (Example: SIGKILL, kill, or 9)."));
196 +
197 + // Verify container is still running after failed restart request
198 + VerifyContainerIsListed(containerId, L"running");
199 + }
200 +
201 + WSLC_TEST_METHOD(WSLCE2E_Container_Restart_InvalidTimeout)
202 + {
203 + // Run a container in the background
204 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
205 + result.Verify({.Stderr = L"", .ExitCode = 0});
206 + const auto containerId = result.GetStdoutOneLine();
207 + VERIFY_IS_FALSE(containerId.empty());
208 +
209 + VerifyContainerIsListed(containerId, L"running");
210 +
211 + result = RunWslc(std::format(L"container restart {} -t abc", containerId));
212 + result.Verify({.Stdout = L"", .ExitCode = 1});
213 + VERIFY_IS_TRUE(
214 + result.StderrContainsSubstring(wsl::shared::Localization::WSLCCLI_InvalidIntegerArgumentError(L"timeout", L"abc")));
215 +
216 + // Verify container is still running after failed restart request
217 + VerifyContainerIsListed(containerId, L"running");
218 + }
219 +
220 +private:
221 + const std::wstring WslcContainerName = L"wslc-test-container";
222 + const std::wstring WslcContainerName2 = L"wslc-test-container-2";
223 + const std::wstring InvalidContainerName = L"wslc-nonexistent-container-for-restart";
224 + const std::wstring InvalidContainerName2 = L"wslc-nonexistent-container-for-restart-2";
225 + const TestImage& DebianImage = DebianTestImage();
226 +};
227 +} // namespace WSLCE2ETests
test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp
+27
@@ -249,9 +249,36 @@ class WSLCE2EContainerStopTests
249 }
250 }
251
252 + WSLC_TEST_METHOD(WSLCE2E_Container_Stop_ContinuesAfterFailure)
253 + {
254 + // Run first container in background
255 + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
256 + result.Verify({.Stderr = L"", .ExitCode = 0});
257 + const auto firstContainerId = result.GetStdoutOneLine();
258 + VERIFY_IS_FALSE(firstContainerId.empty());
259 +
260 + // Run second container in background
261 + result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName2, DebianImage.NameAndTag()));
262 + result.Verify({.Stderr = L"", .ExitCode = 0});
263 + const auto secondContainerId = result.GetStdoutOneLine();
264 + VERIFY_IS_FALSE(secondContainerId.empty());
265 +
266 + // A container that cannot be stopped is reported without skipping the ones after it
267 + result = RunWslc(std::format(L"container stop {} {} {} -t 0", firstContainerId, InvalidContainerName, secondContainerId));
268 + result.Verify(
269 + {.Stdout = std::format(L"{}\r\n{}\r\n", firstContainerId, secondContainerId),
270 + .Stderr = FormatErrorMessage(
271 + std::format(L"Container '{}' not found.", InvalidContainerName), L"WSLC_E_CONTAINER_NOT_FOUND"),
272 + .ExitCode = 1});
273 +
274 + VerifyContainerIsListed(firstContainerId, L"exited");
275 + VerifyContainerIsListed(secondContainerId, L"exited");
276 + }
277 +
278 private:
279 const std::wstring WslcContainerName = L"wslc-test-container";
280 const std::wstring WslcContainerName2 = L"wslc-test-container-2";
281 + const std::wstring InvalidContainerName = L"wslc-nonexistent-container-for-stop";
282 const TestImage& DebianImage = DebianTestImage();
283 };
284 } // namespace WSLCE2ETests