master
cpp 588 lines 24 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCE2EContainerListTests.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 "ContainerModel.h"
17 #include "WSLCExecutor.h"
18 #include "WSLCE2EHelpers.h"
19 #include "TestImageRegistry.h"
20
21 namespace WSLCE2ETests {
22 using namespace wsl::shared;
23
24 using namespace wsl::windows::wslc::models;
25 using namespace wsl::windows::common::string;
26
27 class WSLCE2EContainerListTests
28 {
29 WSLC_TEST_CLASS(WSLCE2EContainerListTests)
30
31 TEST_CLASS_SETUP(ClassSetup)
32 {
33 TestImageRegistry::Instance().EnsureLoaded(DebianImage);
34 return true;
35 }
36
37 TEST_CLASS_CLEANUP(ClassCleanup)
38 {
39 EnsureContainerDoesNotExist(WslcContainerName);
40 EnsureContainerDoesNotExist(WslcContainerName2);
41 return true;
42 }
43
44 TEST_METHOD_SETUP(TestMethodSetup)
45 {
46 EnsureContainerDoesNotExist(WslcContainerName);
47 EnsureContainerDoesNotExist(WslcContainerName2);
48 return true;
49 }
50
51 WSLC_TEST_METHOD(WSLCE2E_Container_List_HelpCommand)
52 {
53 auto result = RunWslc(L"container list --help");
54 result.Verify({.Stderr = L"", .ExitCode = 0});
55 VERIFY_IS_FALSE(result.Stdout.value().empty());
56 }
57
58 WSLC_TEST_METHOD(WSLCE2E_Container_List_AllOption)
59 {
60 VerifyContainerIsNotListed(WslcContainerName);
61
62 // Create a container
63 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
64 result.Verify({.Stderr = L"", .ExitCode = 0});
65 auto containerId = result.GetStdoutOneLine();
66 VERIFY_IS_FALSE(containerId.empty());
67
68 // Find container in list output
69 result = RunWslc(L"container list --no-trunc --all");
70 result.Verify({.Stderr = L"", .ExitCode = 0});
71 auto outputLines = result.GetStdoutLines();
72 std::optional<std::wstring> foundContainerLine{};
73 for (const auto& line : outputLines)
74 {
75 if (line.find(containerId) != std::wstring::npos)
76 {
77 foundContainerLine = line;
78 break;
79 }
80 }
81
82 // Verify we found the container in the list output
83 VERIFY_IS_TRUE(foundContainerLine.has_value());
84 VERIFY_ARE_NOT_EQUAL(std::wstring::npos, foundContainerLine->find(L"Created"));
85 }
86
87 WSLC_TEST_METHOD(WSLCE2E_Container_List_NoOptions_RunningContainers)
88 {
89 VerifyContainerIsNotListed(WslcContainerName);
90
91 // Run a container in the background
92 auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
93 result.Verify({.Stderr = L"", .ExitCode = 0});
94 auto containerId = TruncateId(result.GetStdoutOneLine());
95 VERIFY_IS_FALSE(containerId.empty());
96
97 // Find container in list output with no options
98 result = RunWslc(L"container list");
99 result.Verify({.Stderr = L"", .ExitCode = 0});
100 auto outputLines = result.GetStdoutLines();
101 std::optional<std::wstring> foundContainerLine{};
102 for (const auto& line : outputLines)
103 {
104 if (line.find(containerId) != std::wstring::npos)
105 {
106 foundContainerLine = line;
107 break;
108 }
109 }
110
111 // Verify we found the container in the list output
112 VERIFY_IS_TRUE(foundContainerLine.has_value());
113 VERIFY_ARE_NOT_EQUAL(std::wstring::npos, foundContainerLine->find(L"Up "));
114 }
115
116 WSLC_TEST_METHOD(WSLCE2E_Container_List_NoOptions_ExcludesCreatedContainers)
117 {
118 VerifyContainerIsNotListed(WslcContainerName);
119
120 // Create (but do not start) a container.
121 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
122 result.Verify({.Stderr = L"", .ExitCode = 0});
123 const auto containerId = TruncateId(result.GetStdoutOneLine());
124 VERIFY_IS_FALSE(containerId.empty());
125
126 // Default list should only show running containers.
127 result = RunWslc(L"container list");
128 result.Verify({.Stderr = L"", .ExitCode = 0});
129 bool isListed = false;
130 for (const auto& line : result.GetStdoutLines())
131 {
132 if (line.find(containerId) != std::wstring::npos)
133 {
134 isListed = true;
135 break;
136 }
137 }
138
139 VERIFY_IS_FALSE(isListed);
140 }
141
142 // The table layout must match `docker container list` so users can rely on column order.
143 WSLC_TEST_METHOD(WSLCE2E_Container_List_TableFormat_MatchesDockerColumnOrder)
144 {
145 const auto result = RunWslc(L"container list --all");
146 result.Verify({.Stderr = L"", .ExitCode = 0});
147
148 const auto outputLines = result.GetStdoutLines();
149 VERIFY_IS_FALSE(outputLines.empty());
150
151 const auto& header = outputLines.front();
152 size_t position = 0;
153 for (const auto& column :
154 {Localization::WSLCCLI_TableHeaderContainerId(),
155 Localization::WSLCCLI_TableHeaderImage(),
156 Localization::WSLCCLI_TableHeaderCommand(),
157 Localization::WSLCCLI_TableHeaderCreated(),
158 Localization::WSLCCLI_TableHeaderStatus(),
159 Localization::WSLCCLI_TableHeaderPorts(),
160 Localization::WSLCCLI_TableHeaderNames()})
161 {
162 const auto found = header.find(column, position);
163 VERIFY_ARE_NOT_EQUAL(
164 std::wstring::npos, found, std::format(L"Column '{}' missing or out of order in '{}'", column, header).c_str());
165 position = found + column.size();
166 }
167 }
168
169 WSLC_TEST_METHOD(WSLCE2E_Container_List_QuietOption_OutputsIdsOnly)
170 {
171 VerifyContainerIsNotListed(WslcContainerName);
172
173 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
174 result.Verify({.Stderr = L"", .ExitCode = 0});
175 const auto containerId = result.GetStdoutOneLine();
176 VERIFY_IS_FALSE(containerId.empty());
177
178 result = RunWslc(L"container list --all --quiet");
179 result.Verify({.Stderr = L"", .ExitCode = 0});
180
181 const auto truncatedId = wsl::shared::string::MultiByteToWide(TruncateId(WideToMultiByte(containerId)));
182 VERIFY_ARE_EQUAL(12u, truncatedId.size());
183 VERIFY_IS_TRUE(result.StdoutContainsLine(truncatedId));
184
185 result = RunWslc(L"container list --all --quiet --no-trunc");
186 result.Verify({.Stderr = L"", .ExitCode = 0});
187 VERIFY_IS_TRUE(result.StdoutContainsLine(containerId));
188 }
189
190 WSLC_TEST_METHOD(WSLCE2E_Container_List_InvalidFormatOption)
191 {
192 const auto result = RunWslc(L"container list --format invalid");
193 result.Verify({.Stdout = L"", .ExitCode = 1});
194 VERIFY_IS_TRUE(result.StderrContainsSubstring(
195 L"Invalid format value: invalid is not a recognized format type. Supported format types are: json, table."));
196 }
197
198 WSLC_TEST_METHOD(WSLCE2E_Container_List_JsonFormat)
199 {
200 VerifyContainerIsNotListed(WslcContainerName);
201
202 // Create a container
203 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
204 result.Verify({.Stderr = L"", .ExitCode = 0});
205 const auto containerId = result.GetStdoutOneLine();
206 VERIFY_IS_FALSE(containerId.empty());
207
208 // List containers with json format
209 result = RunWslc(L"container list --all --format json --no-trunc");
210 result.Verify({.Stderr = L"", .ExitCode = 0});
211 // Parse json and verify we got the expected container information back
212 auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(result);
213 VERIFY_IS_GREATER_THAN_OR_EQUAL(containers.size(), 1U);
214 VERIFY_ARE_EQUAL(containers.size(), result.GetStdoutLines().size());
215
216 auto findContainer = [](const std::vector<ContainerOutputInformation>& list, const std::wstring& id) {
217 return std::ranges::any_of(list, [&](const auto& c) { return wsl::shared::string::MultiByteToWide(c.ID) == id; });
218 };
219
220 VERIFY_IS_TRUE(findContainer(containers, containerId));
221
222 // Create another container
223 result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()));
224 result.Verify({.Stderr = L"", .ExitCode = 0});
225 const auto containerId2 = result.GetStdoutOneLine();
226 VERIFY_IS_FALSE(containerId2.empty());
227
228 // List containers with json format again
229 result = RunWslc(L"container list --all --format json --no-trunc");
230 result.Verify({.Stderr = L"", .ExitCode = 0});
231 // Parse json and verify we got both containers back
232 containers = ParseNdjsonOutputAs<ContainerOutputInformation>(result);
233 VERIFY_IS_GREATER_THAN_OR_EQUAL(containers.size(), 2U);
234
235 VERIFY_IS_TRUE(findContainer(containers, containerId));
236 VERIFY_IS_TRUE(findContainer(containers, containerId2));
237 }
238
239 WSLC_TEST_METHOD(WSLCE2E_Container_List_JsonFormat_MatchesDockerShape)
240 {
241 const std::set<std::string> expectedKeys = {
242 "Command",
243 "CreatedAt",
244 "HealthStatus",
245 "ID",
246 "Image",
247 "Labels",
248 "LocalVolumes",
249 "Mounts",
250 "Names",
251 "Networks",
252 "Platform",
253 "Ports",
254 "RunningFor",
255 "Size",
256 "State",
257 "Status"};
258
259 VerifyContainerIsNotListed(WslcContainerName);
260
261 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
262 result.Verify({.Stderr = L"", .ExitCode = 0});
263
264 result = RunWslc(L"container list --all --format json");
265 result.Verify({.Stderr = L"", .ExitCode = 0});
266
267 const auto entries = ParseNdjsonOutput(result);
268 VERIFY_IS_GREATER_THAN_OR_EQUAL(entries.size(), 1u);
269
270 for (const auto& entry : entries)
271 {
272 std::set<std::string> keys;
273 for (const auto& [key, value] : entry.items())
274 {
275 keys.insert(key);
276
277 if (key == "Platform")
278 {
279 VERIFY_IS_TRUE(value.is_object());
280 }
281 else
282 {
283 VERIFY_IS_TRUE(
284 value.is_string(), wsl::shared::string::MultiByteToWide(std::format("'{}' must be a string", key)).c_str());
285 }
286 }
287
288 VERIFY_ARE_EQUAL(expectedKeys, keys, L"json output must contain exactly docker's container fields");
289
290 VERIFY_ARE_EQUAL(12u, entry["ID"].get<std::string>().size());
291 VERIFY_ARE_NOT_EQUAL(std::string{}, entry["Names"].get<std::string>());
292 VERIFY_ARE_NOT_EQUAL(std::string{}, entry["State"].get<std::string>());
293 VERIFY_ARE_EQUAL(std::string{"linux"}, entry["Platform"]["os"].get<std::string>());
294 }
295 }
296
297 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_InvalidKey)
298 {
299 // Filter keys are validated by the Docker daemon, which rejects unknown keys.
300 const auto result = RunWslc(L"container list --filter color=blue");
301 VERIFY_ARE_EQUAL(1, result.ExitCode);
302 VERIFY_IS_TRUE(result.Stderr.has_value());
303 VERIFY_ARE_NOT_EQUAL(std::wstring::npos, result.Stderr->find(L"invalid filter 'color'"));
304 }
305
306 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_MalformedValue)
307 {
308 // Filter values must be of the form key=value; bare keys are rejected by the CLI.
309 const auto result = RunWslc(L"container list --filter status");
310 result.Verify({.Stdout = L"", .ExitCode = 1});
311 VERIFY_IS_TRUE(result.StderrContainsSubstring(Localization::WSLCCLI_InvalidFilterError(L"status")));
312 }
313
314 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_InvalidStatusValue)
315 {
316 // Status values are validated by the Docker daemon, which rejects unknown values.
317 const auto result = RunWslc(L"container list --filter status=bogus");
318 VERIFY_ARE_EQUAL(1, result.ExitCode);
319 VERIFY_IS_TRUE(result.Stderr.has_value());
320 VERIFY_ARE_NOT_EQUAL(std::wstring::npos, result.Stderr->find(L"invalid filter 'status=bogus'"));
321 }
322
323 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_Name)
324 {
325 VerifyContainerIsNotListed(WslcContainerName);
326 VerifyContainerIsNotListed(WslcContainerName2);
327
328 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
329 result.Verify({.Stderr = L"", .ExitCode = 0});
330 const auto containerId = result.GetStdoutOneLine();
331
332 result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()));
333 result.Verify({.Stderr = L"", .ExitCode = 0});
334 const auto containerId2 = result.GetStdoutOneLine();
335
336 result = RunWslc(std::format(L"container list --all --format json --filter name={}", WslcContainerName2));
337 result.Verify({.Stderr = L"", .ExitCode = 0});
338
339 const auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(result);
340 VERIFY_ARE_EQUAL(1U, containers.size());
341 VERIFY_ARE_EQUAL(WideToMultiByte(WslcContainerName2), std::string(containers[0].Names));
342 }
343
344 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_Status)
345 {
346 VerifyContainerIsNotListed(WslcContainerName);
347 VerifyContainerIsNotListed(WslcContainerName2);
348
349 // Created (never started) container.
350 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
351 result.Verify({.Stderr = L"", .ExitCode = 0});
352
353 // Running container.
354 result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName2, DebianImage.NameAndTag()));
355 result.Verify({.Stderr = L"", .ExitCode = 0});
356
357 auto listNames = [&](const std::wstring& filterArgs) {
358 auto r = RunWslc(std::format(L"container list --all --format json {}", filterArgs));
359 r.Verify({.Stderr = L"", .ExitCode = 0});
360 const auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(r);
361 std::set<std::string> names;
362 for (const auto& c : containers)
363 {
364 names.insert(c.Names);
365 }
366 return names;
367 };
368
369 // status=created
370 {
371 const auto names = listNames(L"--filter status=created");
372 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName)));
373 VERIFY_IS_FALSE(names.contains(WideToMultiByte(WslcContainerName2)));
374 }
375
376 // status=running
377 {
378 const auto names = listNames(L"--filter status=running");
379 VERIFY_IS_FALSE(names.contains(WideToMultiByte(WslcContainerName)));
380 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName2)));
381 }
382
383 // Multiple --filter status= values are OR'd.
384 {
385 const auto names = listNames(L"--filter status=created --filter status=running");
386 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName)));
387 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName2)));
388 }
389 }
390
391 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_Label)
392 {
393 VerifyContainerIsNotListed(WslcContainerName);
394 VerifyContainerIsNotListed(WslcContainerName2);
395
396 // First container has both labels; second has only one of them.
397 auto result = RunWslc(std::format(
398 L"container create --name {} --label filter.test=yes --label filter.role=primary {}", WslcContainerName, DebianImage.NameAndTag()));
399 result.Verify({.Stderr = L"", .ExitCode = 0});
400
401 result = RunWslc(std::format(L"container create --name {} --label filter.test=yes {}", WslcContainerName2, DebianImage.NameAndTag()));
402 result.Verify({.Stderr = L"", .ExitCode = 0});
403
404 auto listNames = [&](const std::wstring& filterArgs) {
405 auto r = RunWslc(std::format(L"container list --all --format json {}", filterArgs));
406 r.Verify({.Stderr = L"", .ExitCode = 0});
407 const auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(r);
408 std::set<std::string> names;
409 for (const auto& c : containers)
410 {
411 names.insert(c.Names);
412 }
413 return names;
414 };
415
416 // label=<key> matches both since both have filter.test set (any value).
417 {
418 const auto names = listNames(L"--filter label=filter.test");
419 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName)));
420 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName2)));
421 }
422
423 // label=<key>=<value> matches only the first.
424 {
425 const auto names = listNames(L"--filter label=filter.role=primary");
426 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName)));
427 VERIFY_IS_FALSE(names.contains(WideToMultiByte(WslcContainerName2)));
428 }
429
430 // Multiple --filter label= entries are AND'd.
431 {
432 const auto names = listNames(L"--filter label=filter.test --filter label=filter.role=primary");
433 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName)));
434 VERIFY_IS_FALSE(names.contains(WideToMultiByte(WslcContainerName2)));
435 }
436 }
437
438 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_Id)
439 {
440 VerifyContainerIsNotListed(WslcContainerName);
441 VerifyContainerIsNotListed(WslcContainerName2);
442
443 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
444 result.Verify({.Stderr = L"", .ExitCode = 0});
445 const auto containerId = result.GetStdoutOneLine();
446
447 result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()));
448 result.Verify({.Stderr = L"", .ExitCode = 0});
449
450 // Filter by id (full id) should return exactly one container.
451 result = RunWslc(std::format(L"container list --all --format json --no-trunc --filter id={}", containerId));
452 result.Verify({.Stderr = L"", .ExitCode = 0});
453
454 const auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(result);
455 VERIFY_ARE_EQUAL(1U, containers.size());
456 VERIFY_ARE_EQUAL(WideToMultiByte(containerId), std::string(containers[0].ID));
457 }
458
459 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_Exited)
460 {
461 VerifyContainerIsNotListed(WslcContainerName);
462 VerifyContainerIsNotListed(WslcContainerName2);
463
464 // First container exits with code 0.
465 auto result = RunWslc(std::format(L"container run --name {} {} true", WslcContainerName, DebianImage.NameAndTag()));
466 result.Verify({.ExitCode = 0});
467
468 // Second container exits with non-zero code.
469 result = RunWslc(std::format(L"container run --name {} {} sh -c \"exit 7\"", WslcContainerName2, DebianImage.NameAndTag()));
470 // run with non-zero container exit code is allowed; we don't assert exit here.
471
472 auto listNames = [&](const std::wstring& filterArgs) {
473 auto r = RunWslc(std::format(L"container list --all --format json {}", filterArgs));
474 r.Verify({.Stderr = L"", .ExitCode = 0});
475 const auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(r);
476 std::set<std::string> names;
477 for (const auto& c : containers)
478 {
479 names.insert(c.Names);
480 }
481 return names;
482 };
483
484 // exited=0 should match the first container only.
485 {
486 const auto names = listNames(L"--filter exited=0");
487 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName)));
488 VERIFY_IS_FALSE(names.contains(WideToMultiByte(WslcContainerName2)));
489 }
490
491 // exited=7 should match the second container only.
492 {
493 const auto names = listNames(L"--filter exited=7");
494 VERIFY_IS_FALSE(names.contains(WideToMultiByte(WslcContainerName)));
495 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName2)));
496 }
497 }
498
499 WSLC_TEST_METHOD(WSLCE2E_Container_List_Filter_BeforeSince)
500 {
501 VerifyContainerIsNotListed(WslcContainerName);
502 VerifyContainerIsNotListed(WslcContainerName2);
503
504 // Create the first container, then the second so that ordering is deterministic.
505 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
506 result.Verify({.Stderr = L"", .ExitCode = 0});
507
508 result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()));
509 result.Verify({.Stderr = L"", .ExitCode = 0});
510
511 auto listNames = [&](const std::wstring& filterArgs) {
512 auto r = RunWslc(std::format(L"container list --all --format json {}", filterArgs));
513 r.Verify({.Stderr = L"", .ExitCode = 0});
514 const auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(r);
515 std::set<std::string> names;
516 for (const auto& c : containers)
517 {
518 names.insert(c.Names);
519 }
520 return names;
521 };
522
523 // before=<container2 name> -> only container1 (created earlier) is visible.
524 {
525 const auto names = listNames(std::format(L"--filter before={}", WslcContainerName2));
526 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName)));
527 VERIFY_IS_FALSE(names.contains(WideToMultiByte(WslcContainerName2)));
528 }
529
530 // since=<container1 name> -> only container2 (created later) is visible.
531 {
532 const auto names = listNames(std::format(L"--filter since={}", WslcContainerName));
533 VERIFY_IS_FALSE(names.contains(WideToMultiByte(WslcContainerName)));
534 VERIFY_IS_TRUE(names.contains(WideToMultiByte(WslcContainerName2)));
535 }
536 }
537
538 WSLC_TEST_METHOD(WSLCE2E_Container_List_LastAndLatest)
539 {
540 VerifyContainerIsNotListed(WslcContainerName);
541 VerifyContainerIsNotListed(WslcContainerName2);
542
543 // Create container1 then container2 (deterministic order).
544 auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
545 result.Verify({.Stderr = L"", .ExitCode = 0});
546
547 result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName2, DebianImage.NameAndTag()));
548 result.Verify({.Stderr = L"", .ExitCode = 0});
549
550 // --latest is shorthand for --last 1; should return only container2 (most recent).
551 // Implies --all so created-but-not-running containers are visible.
552 {
553 result = RunWslc(L"container list --latest --format json");
554 result.Verify({.Stderr = L"", .ExitCode = 0});
555
556 const auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(result);
557 VERIFY_ARE_EQUAL(1U, containers.size());
558 VERIFY_ARE_EQUAL(WideToMultiByte(WslcContainerName2), std::string(containers[0].Names));
559 }
560
561 // --last 2 should cap output at 2 containers.
562 {
563 result = RunWslc(L"container list --last 2 --format json");
564 result.Verify({.Stderr = L"", .ExitCode = 0});
565
566 const auto containers = ParseNdjsonOutputAs<ContainerOutputInformation>(result);
567 VERIFY_IS_TRUE(containers.size() <= 2u);
568 }
569
570 // --last and --latest are mutually exclusive.
571 {
572 result = RunWslc(L"container list --last 1 --latest");
573 VERIFY_ARE_EQUAL(1, result.ExitCode);
574 }
575
576 // --last requires an integer.
577 {
578 result = RunWslc(L"container list --last bogus");
579 VERIFY_ARE_EQUAL(1, result.ExitCode);
580 }
581 }
582
583 private:
584 const std::wstring WslcContainerName = L"wslc-test-container";
585 const std::wstring WslcContainerName2 = L"wslc-test-container-2";
586 const TestImage& DebianImage = DebianTestImage();
587 };
588 } // namespace WSLCE2ETests