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