master
cpp 197 lines 7.83 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCE2EImagePruneTests.cpp
8
9 Abstract:
10
11 This file contains end-to-end tests for WSLC image prune command.
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 WSLCE2EImagePruneTests
24 {
25 WSLC_TEST_CLASS(WSLCE2EImagePruneTests)
26
27 TEST_CLASS_SETUP(ClassSetup)
28 {
29 TestImageRegistry::Instance().EnsureLoaded(DebianImage);
30 return true;
31 }
32
33 TEST_CLASS_CLEANUP(ClassCleanup)
34 {
35 TestImageRegistry::Instance().EnsureLoaded(DebianImage);
36 return true;
37 }
38
39 WSLC_TEST_METHOD(WSLCE2E_Image_Prune_HelpCommand)
40 {
41 const auto result = RunWslc(L"image prune --help");
42 result.Verify({.Stderr = L"", .ExitCode = 0});
43 VERIFY_IS_FALSE(result.Stdout.value().empty());
44 }
45
46 WSLC_TEST_METHOD(WSLCE2E_Image_Prune_NoDanglingImages)
47 {
48 // Prune when no dangling images exist should succeed with zero reclaimed space
49 const auto result = RunWslc(L"image prune");
50 result.Verify({.Stderr = L"", .ExitCode = 0});
51
52 VerifyStdoutContains(result, L"Total reclaimed space:");
53 }
54
55 WSLC_TEST_METHOD(WSLCE2E_Image_Prune_DanglingImage)
56 {
57 // Create a dangling image by overwriting its only tag with a different image.
58 // 1. Tag debian as prune-target:v1
59 // 2. Delete the original debian:latest tag so prune-target:v1 is the only reference
60 // 3. Tag alpine as prune-target:v1, overwriting it — debian image is now dangling
61 TestImageRegistry::Instance().EnsureLoaded(AlpineImage);
62 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
63 RunWslc(L"image prune");
64 RunWslc(L"image delete prune-target:v1");
65 TestImageRegistry::Instance().Delete(AlpineImage);
66 TestImageRegistry::Instance().Restore(DebianImage);
67 });
68
69 RunWslc(std::format(L"image tag {} prune-target:v1", DebianImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
70 RunWslc(std::format(L"image delete {}", DebianImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
71 RunWslc(std::format(L"image tag {} prune-target:v1", AlpineImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
72
73 // Now prune should remove the dangling (original debian) image
74 const auto result = RunWslc(L"image prune");
75 result.Verify({.Stderr = L"", .ExitCode = 0});
76
77 bool foundDeleted = false;
78 for (const auto& line : result.GetStdoutLines())
79 {
80 if (line.find(L"deleted:") != std::wstring::npos || line.find(L"untagged:") != std::wstring::npos)
81 {
82 foundDeleted = true;
83 break;
84 }
85 }
86
87 VERIFY_IS_TRUE(foundDeleted, L"Expected pruned image output");
88 VerifyStdoutContains(result, L"Total reclaimed space:");
89
90 // Verify alpine image is still present (prune should only remove dangling images)
91 VerifyImageIsListed(AlpineImage);
92 }
93
94 WSLC_TEST_METHOD(WSLCE2E_Image_Prune_AllFlag)
95 {
96 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
97 TestImageRegistry::Instance().InvalidateSession();
98 TestImageRegistry::Instance().Restore(DebianImage);
99 });
100
101 // --all should prune unused images (not just dangling)
102 const auto result = RunWslc(L"image prune --all");
103 result.Verify({.Stderr = L"", .ExitCode = 0});
104
105 VerifyStdoutContains(result, L"Total reclaimed space:");
106
107 // Verify the image was actually pruned
108 auto listResult = RunWslc(L"image list");
109 listResult.Verify({.Stderr = L"", .ExitCode = 0});
110 for (const auto& line : listResult.GetStdoutLines())
111 {
112 VERIFY_IS_FALSE(
113 line.find(DebianImage.NameAndTag()) != std::wstring::npos,
114 std::format(L"Image '{}' should have been pruned by --all", DebianImage.NameAndTag()).c_str());
115 }
116 }
117
118 WSLC_TEST_METHOD(WSLCE2E_Image_Prune_Filter_MalformedValue)
119 {
120 // Filter values must be of the form key=value; bare keys are rejected by the CLI.
121 const auto result = RunWslc(L"image prune --filter label");
122 result.Verify({.Stdout = L"", .ExitCode = 1});
123 VERIFY_IS_TRUE(result.StderrContainsSubstring(Localization::WSLCCLI_InvalidFilterError(L"label")));
124 }
125
126 WSLC_TEST_METHOD(WSLCE2E_Image_Prune_Filter_InvalidKey)
127 {
128 // Filter keys are validated by the Docker daemon, which rejects unknown keys.
129 const auto result = RunWslc(L"image prune --filter color=blue");
130 VERIFY_ARE_EQUAL(1, result.ExitCode);
131 VERIFY_IS_TRUE(result.Stderr.has_value());
132 VERIFY_ARE_NOT_EQUAL(std::wstring::npos, result.Stderr->find(L"invalid filter"));
133 }
134
135 WSLC_TEST_METHOD(WSLCE2E_Image_Prune_Filter_LabelPreservesDangling)
136 {
137 // Create a dangling debian image (same trick as WSLCE2E_Image_Prune_DanglingImage).
138 TestImageRegistry::Instance().EnsureLoaded(AlpineImage);
139 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
140 RunWslc(L"image prune");
141 RunWslc(L"image delete prune-target:v1");
142 TestImageRegistry::Instance().Delete(AlpineImage);
143 TestImageRegistry::Instance().Restore(DebianImage);
144 });
145
146 RunWslc(std::format(L"image tag {} prune-target:v1", DebianImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
147 RunWslc(std::format(L"image delete {}", DebianImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
148 RunWslc(std::format(L"image tag {} prune-target:v1", AlpineImage.NameAndTag())).Verify({.Stderr = L"", .ExitCode = 0});
149
150 // Prune only dangling images carrying a label the dangling image does NOT have.
151 // Multiple --filter label= values are AND'd by the daemon; the dangling image
152 // matches neither, so it must survive this prune.
153 auto filteredPrune = RunWslc(L"image prune --filter label=wslc.test.never=present --filter label=wslc.test.also=missing");
154 filteredPrune.Verify({.Stderr = L"", .ExitCode = 0});
155 for (const auto& line : filteredPrune.GetStdoutLines())
156 {
157 VERIFY_IS_FALSE(
158 line.find(L"deleted:") != std::wstring::npos, L"Filtered prune should not have deleted the dangling image");
159 VERIFY_IS_FALSE(
160 line.find(L"untagged:") != std::wstring::npos, L"Filtered prune should not have untagged the dangling image");
161 }
162
163 // A subsequent unfiltered prune should still find and remove the dangling image,
164 // proving the filter — not the absence of dangling images — was the reason nothing
165 // was pruned above.
166 auto unfilteredPrune = RunWslc(L"image prune");
167 unfilteredPrune.Verify({.Stderr = L"", .ExitCode = 0});
168 bool foundDeleted = false;
169 for (const auto& line : unfilteredPrune.GetStdoutLines())
170 {
171 if (line.find(L"deleted:") != std::wstring::npos || line.find(L"untagged:") != std::wstring::npos)
172 {
173 foundDeleted = true;
174 break;
175 }
176 }
177 VERIFY_IS_TRUE(foundDeleted, L"Expected the dangling image to be pruned by the unfiltered call");
178 }
179
180 private:
181 const TestImage& DebianImage = DebianTestImage();
182 const TestImage& AlpineImage = AlpineTestImage();
183
184 static void VerifyStdoutContains(const WSLCExecutionResult& result, const std::wstring& substring)
185 {
186 for (const auto& line : result.GetStdoutLines())
187 {
188 if (line.find(substring) != std::wstring::npos)
189 {
190 return;
191 }
192 }
193
194 VERIFY_FAIL(std::format(L"Expected stdout to contain '{}'", substring).c_str());
195 }
196 };
197 } // namespace WSLCE2ETests