master
cpp 301 lines 12.6 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCE2EContainerRemoveTests.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 WSLCE2EContainerRemoveTests
24 {
25 WSLC_TEST_CLASS(WSLCE2EContainerRemoveTests)
26
27 TEST_CLASS_SETUP(ClassSetup)
28 {
29 TestImageRegistry::Instance().EnsureLoaded(DebianImage);
30 BuildAnonymousVolumeImage();
31 return true;
32 }
33
34 TEST_CLASS_CLEANUP(ClassCleanup)
35 {
36 EnsureContainerDoesNotExist(WslcContainerName);
37 EnsureContainerDoesNotExist(WslcContainerName2);
38 EnsureVolumeDoesNotExist(TestVolumeName);
39 TestImageRegistry::Instance().Delete(AnonymousVolumeImage);
40 return true;
41 }
42
43 TEST_METHOD_SETUP(TestMethodSetup)
44 {
45 EnsureContainerDoesNotExist(WslcContainerName);
46 EnsureContainerDoesNotExist(WslcContainerName2);
47 EnsureVolumeDoesNotExist(TestVolumeName);
48 return true;
49 }
50
51 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_HelpCommand)
52 {
53 auto result = RunWslc(L"container remove --help");
54 result.Verify({.Stderr = L"", .ExitCode = 0});
55 VERIFY_IS_FALSE(result.Stdout.value().empty());
56 }
57
58 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_NotFound)
59 {
60 VerifyContainerIsNotListed(WslcContainerName);
61
62 auto result = RunWslc(std::format(L"container remove {}", WslcContainerName));
63 result.Verify(
64 {.Stdout = L"",
65 .Stderr =
66 FormatErrorMessage(std::format(L"Container '{}' not found.", WslcContainerName), L"WSLC_E_CONTAINER_NOT_FOUND"),
67 .ExitCode = 1});
68 }
69
70 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_Valid)
71 {
72 VerifyContainerIsNotListed(WslcContainerName);
73
74 // Create the container with a valid image
75 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
76 result.Verify({.Stderr = L"", .ExitCode = 0});
77 std::wstring containerId = result.GetStdoutOneLine();
78
79 // Verify the container is listed with the correct status
80 VerifyContainerIsListed(containerId, L"created");
81
82 // Delete the container
83 result = RunWslc(std::format(L"container remove {}", WslcContainerName));
84 result.Verify({.Stdout = std::format(L"{}\r\n", WslcContainerName), .Stderr = L"", .ExitCode = 0});
85
86 // Verify the container is no longer listed
87 VerifyContainerIsNotListed(WslcContainerName);
88 }
89
90 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_ById_Valid)
91 {
92 VerifyContainerIsNotListed(WslcContainerName);
93
94 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
95 result.Verify({.Stderr = L"", .ExitCode = 0});
96 const auto containerId = result.GetStdoutOneLine();
97 VERIFY_IS_FALSE(containerId.empty());
98
99 VerifyContainerIsListed(containerId, L"created");
100
101 result = RunWslc(std::format(L"container remove {}", containerId));
102 result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0});
103
104 VerifyContainerIsNotListed(containerId);
105 VerifyContainerIsNotListed(WslcContainerName);
106 }
107
108 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_Force_RunningContainer)
109 {
110 VerifyContainerIsNotListed(WslcContainerName);
111
112 // Run a container so it is in running state
113 auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
114 result.Verify({.Stderr = L"", .ExitCode = 0});
115 const auto containerId = result.GetStdoutOneLine();
116 VERIFY_IS_FALSE(containerId.empty());
117
118 VerifyContainerIsListed(containerId, L"running");
119
120 // Removing without force should fail
121 result = RunWslc(std::format(L"container remove {}", containerId));
122
123 // TODO Add .Stderr after this issue is resolved:
124 // https://github.com/microsoft/WSL/issues/14510
125 result.Verify({.ExitCode = 1});
126
127 // Container should still exist and be running
128 VerifyContainerIsListed(containerId, L"running");
129
130 // Removing with force should succeed
131 result = RunWslc(std::format(L"container remove --force {}", containerId));
132 result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0});
133
134 VerifyContainerIsNotListed(containerId);
135 VerifyContainerIsNotListed(WslcContainerName);
136 }
137
138 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_Multiple_Valid)
139 {
140 VerifyContainerIsNotListed(WslcContainerName);
141 VerifyContainerIsNotListed(WslcContainerName2);
142
143 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
144 result.Verify({.Stderr = L"", .ExitCode = 0});
145 const auto containerId1 = result.GetStdoutOneLine();
146 VERIFY_IS_FALSE(containerId1.empty());
147
148 result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()));
149 result.Verify({.Stderr = L"", .ExitCode = 0});
150 const auto containerId2 = result.GetStdoutOneLine();
151 VERIFY_IS_FALSE(containerId2.empty());
152
153 VerifyContainerIsListed(containerId1, L"created");
154 VerifyContainerIsListed(containerId2, L"created");
155
156 result = RunWslc(std::format(L"container remove {} {}", containerId1, containerId2));
157 result.Verify({.Stdout = std::format(L"{}\r\n{}\r\n", containerId1, containerId2), .Stderr = L"", .ExitCode = 0});
158
159 VerifyContainerIsNotListed(containerId1);
160 VerifyContainerIsNotListed(containerId2);
161 VerifyContainerIsNotListed(WslcContainerName);
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()));
195 result.Verify({.Stderr = L"", .ExitCode = 0});
196
197 const auto anonymousVolume = GetAnonymousVolumeName(WslcContainerName);
198 VerifyVolumeIsListed(anonymousVolume);
199
200 result = RunWslc(std::format(L"container remove --volumes {}", WslcContainerName));
201 result.Verify({.Stdout = std::format(L"{}\r\n", WslcContainerName), .Stderr = L"", .ExitCode = 0});
202
203 VerifyContainerIsNotListed(WslcContainerName);
204 VerifyVolumeIsNotListed(anonymousVolume);
205 }
206
207 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_WithoutVolumes_KeepsAnonymousVolume)
208 {
209 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, AnonymousVolumeImage.NameAndTag()));
210 result.Verify({.Stderr = L"", .ExitCode = 0});
211
212 const auto anonymousVolume = GetAnonymousVolumeName(WslcContainerName);
213 auto cleanup = wil::scope_exit([&]() { EnsureVolumeDoesNotExist(anonymousVolume); });
214 VerifyVolumeIsListed(anonymousVolume);
215
216 result = RunWslc(std::format(L"container remove {}", WslcContainerName));
217 result.Verify({.Stdout = std::format(L"{}\r\n", WslcContainerName), .Stderr = L"", .ExitCode = 0});
218
219 VerifyContainerIsNotListed(WslcContainerName);
220 VerifyVolumeIsListed(anonymousVolume);
221 }
222
223 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_Volumes_KeepsNamedVolume)
224 {
225 auto result = RunWslc(std::format(L"volume create {}", TestVolumeName));
226 result.Verify({.Stderr = L"", .ExitCode = 0});
227
228 result = RunWslc(std::format(L"container create --name {} -v {}:/data {}", WslcContainerName, TestVolumeName, DebianImage.NameAndTag()));
229 result.Verify({.Stderr = L"", .ExitCode = 0});
230
231 result = RunWslc(std::format(L"container remove --volumes {}", WslcContainerName));
232 result.Verify({.Stdout = std::format(L"{}\r\n", WslcContainerName), .Stderr = L"", .ExitCode = 0});
233
234 VerifyContainerIsNotListed(WslcContainerName);
235 VerifyVolumeIsListed(TestVolumeName);
236 }
237
238 WSLC_TEST_METHOD(WSLCE2E_Container_Remove_Volumes_Force_RunningContainer)
239 {
240 auto result =
241 RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, AnonymousVolumeImage.NameAndTag()));
242 result.Verify({.Stderr = L"", .ExitCode = 0});
243 const auto containerId = result.GetStdoutOneLine();
244 VERIFY_IS_FALSE(containerId.empty());
245
246 const auto anonymousVolume = GetAnonymousVolumeName(WslcContainerName);
247 VerifyVolumeIsListed(anonymousVolume);
248 VerifyContainerIsListed(containerId, L"running");
249
250 // -fv exercises the combined short form.
251 result = RunWslc(std::format(L"container rm -fv {}", containerId));
252 result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0});
253
254 VerifyContainerIsNotListed(containerId);
255 VerifyVolumeIsNotListed(anonymousVolume);
256 }
257
258 private:
259 // The VOLUME directive is the only way to get an anonymous volume: `-v` requires source:target.
260 void BuildAnonymousVolumeImage()
261 {
262 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-container-remove-volume-image";
263 auto cleanup = SetupTestDirectory(testRoot);
264
265 auto dockerfilePath = testRoot / L"Dockerfile";
266 WriteTestFileContent(
267 dockerfilePath, std::format("FROM {}\nVOLUME /data\n", wsl::shared::string::WideToMultiByte(DebianImage.NameAndTag())));
268
269 auto result = RunWslc(std::format(
270 L"build \"{}\" -f \"{}\" -t {}", testRoot.wstring(), dockerfilePath.wstring(), AnonymousVolumeImage.NameAndTag()));
271 result.Verify({.ExitCode = 0});
272 }
273
274 static std::wstring GetAnonymousVolumeName(const std::wstring& containerName)
275 {
276 const auto inspect = InspectContainer(containerName);
277 const auto mount = std::ranges::find_if(inspect.Mounts, [](const auto& entry) { return entry.Type == "volume"; });
278
279 VERIFY_IS_TRUE(mount != inspect.Mounts.end(), L"Container inspect did not return the anonymous volume mount");
280 VERIFY_IS_FALSE(mount->Name.empty(), L"Container inspect returned an empty anonymous volume name");
281 VERIFY_IS_TRUE(mount->Source.empty(), L"Anonymous volume source must be empty");
282 VERIFY_ARE_EQUAL(mount->Destination, "/data");
283
284 const auto volumeName = wsl::shared::string::MultiByteToWide(mount->Name);
285 const auto volumeLabels = InspectVolume(volumeName).Labels;
286 VERIFY_IS_TRUE(
287 volumeLabels.has_value() && volumeLabels->contains("com.docker.volume.anonymous"),
288 L"The volume returned by container inspect is not anonymous");
289 return volumeName;
290 }
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
298 // Derived from DebianImage, so it must be deleted first in ClassCleanup.
299 const TestImage AnonymousVolumeImage{.Name = L"wslc-e2e-container-remove-anon", .Tag = L"latest"};
300 };
301 } // namespace WSLCE2ETests