Simplify e2e build-test image cleanup with per-test RAII ownership (#41094)

* Simplify e2e build-test image cleanup with per-test RAII ownership Replace the hand-maintained DeleteAllBuiltImages() static list (run in both TEST_METHOD_SETUP and TEST_METHOD_CLEANUP, sweeping every built image twice per test and flooding the log with VERIFY lines) with: - A per-test DeleteImageOnExit() RAII guard so each test owns and cleans up exactly the image(s) it builds. It is best-effort and non-throwing (no VERIFY) since it may run during stack unwinding after a failure. - A single class-level DeleteImagesWithRepositoryPrefix() prune in ClassSetup and ClassCleanup as the authoritative safety net for images left behind by a crashed run. This removes the per-method sweeps, the static image list, and the excessive per-field VERIFY logging that dominated test output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: John Stephens <johnstep@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

ggarzia-MSFT committed Jul 16, 2026 at 17:06 UTC f8a67e999c8a62072bc11e869b54705bbbb265b6
3 files changed +48 -24
test/windows/wslc/e2e/WSLCE2EHelpers.cpp
+20
@@ -392,6 +392,26 @@ void EnsureImageIsDeleted(const TestImage& image)
392 }
393 }
394
395 +void DeleteImagesWithRepositoryPrefix(const std::wstring& repositoryPrefix)
396 +{
397 + auto result = RunWslc(L"image list --format json");
398 + result.Verify({.Stderr = L"", .ExitCode = 0});
399 +
400 + const auto images = wsl::shared::FromJson<std::vector<wsl::windows::wslc::models::ImageInformation>>(result.Stdout.value().c_str());
401 + const auto prefix = wsl::shared::string::WideToMultiByte(repositoryPrefix);
402 + for (const auto& image : images)
403 + {
404 + if (image.Repository && image.Tag && image.Repository->starts_with(prefix))
405 + {
406 + // No container cleanup here: the images this prunes are only ever built and inspected, never used to
407 + // create containers, so image delete --force is sufficient. If a future test containerizes a built
408 + // image, remove its container in that test's cleanup rather than broadening this prefix-based safety net.
409 + const auto nameAndTag = wsl::shared::string::MultiByteToWide(std::format("{}:{}", *image.Repository, *image.Tag));
410 + RunWslc(std::format(L"image delete --force {}", nameAndTag)).Verify({.Stderr = L"", .ExitCode = 0});
411 + }
412 + }
413 +}
414 +
415 void EnsureNoUntaggedImages()
416 {
417 auto result = RunWslc(L"image list --format json --filter dangling=true");
test/windows/wslc/e2e/WSLCE2EHelpers.h
+1
@@ -127,6 +127,7 @@ std::vector<wsl::windows::wslc::models::ContainerInformation> ListAllContainers(
127 void EnsureContainerDoesNotExist(const std::wstring& containerName);
128 void EnsureImageIsLoaded(const TestImage& image, const std::wstring& sessionName = L"");
129 void EnsureImageIsDeleted(const TestImage& image);
130 +void DeleteImagesWithRepositoryPrefix(const std::wstring& repositoryPrefix);
131 void EnsureImageContainersAreDeleted(const TestImage& image);
132 void EnsureNoUntaggedImages();
133 void EnsureSessionIsTerminated(const std::wstring& sessionName = L"");
test/windows/wslc/e2e/WSLCE2EImageBuildTests.cpp
+27 -24
@@ -24,32 +24,40 @@ class WSLCE2EImageBuildTests
24
25 TEST_CLASS_SETUP(ClassSetup)
26 {
27 - DeleteAllBuiltImages();
27 + DeleteImagesWithRepositoryPrefix(c_builtImagePrefix);
28 EnsureImageIsLoaded(DebianTestImage());
29 return true;
30 }
31
32 TEST_CLASS_CLEANUP(ClassCleanup)
33 {
34 - DeleteAllBuiltImages();
34 + DeleteImagesWithRepositoryPrefix(c_builtImagePrefix);
35 EnsureImageIsDeleted(DebianTestImage());
36 return true;
37 }
38
39 - TEST_METHOD_SETUP(MethodSetup)
40 - {
41 - DeleteAllBuiltImages();
42 - return true;
43 - }
39 + // Each test owns and cleans up exactly the image(s) it builds via DeleteImageOnExit, so there is
40 + // no per-method sweep. DeleteImagesWithRepositoryPrefix in the class setup/cleanup above is only a
41 + // safety net for images left behind by a crashed run.
42 + static constexpr auto c_builtImagePrefix = L"wslc-e2e-build-";
43
45 - TEST_METHOD_CLEANUP(MethodCleanup)
44 + // Returns an RAII guard that best-effort deletes the given image when it goes out of scope. It is
45 + // deliberately non-throwing (no VERIFY) because it may run while the stack unwinds after a test
46 + // failure; the class-level prune is the authoritative cleanup.
47 + static auto DeleteImageOnExit(const TestImage& image)
48 {
47 - DeleteAllBuiltImages();
48 - return true;
49 + return wil::scope_exit([image]() {
50 + try
51 + {
52 + RunWslc(std::format(L"image delete --force {}", image.NameAndTag()));
53 + }
54 + CATCH_LOG()
55 + });
56 }
57
58 WSLC_TEST_METHOD(WSLCE2E_Image_Build_EmptyContextDirectory_Success)
59 {
60 + auto imageCleanup = DeleteImageOnExit(BuiltImage);
61 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-empty-context";
62 auto cleanup = SetupTestDirectory(testRoot);
63
@@ -73,6 +81,8 @@ class WSLCE2EImageBuildTests
81
82 WSLC_TEST_METHOD(WSLCE2E_Image_Build_BuildArgsFileAndMultipleTags_Success)
83 {
84 + auto imageCleanup1 = DeleteImageOnExit(BuiltImageTag1);
85 + auto imageCleanup2 = DeleteImageOnExit(BuiltImageTag2);
86 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-args-tags";
87 auto cleanup = SetupTestDirectory(testRoot);
88
@@ -124,6 +134,7 @@ class WSLCE2EImageBuildTests
134 {
135 SKIP_TEST_UNSTABLE(); // TODO: Enable when a private image source is available.
136
137 + auto imageCleanup = DeleteImageOnExit(BuiltImagePull);
138 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-pull";
139 auto cleanup = SetupTestDirectory(testRoot);
140
@@ -148,6 +159,7 @@ class WSLCE2EImageBuildTests
159
160 WSLC_TEST_METHOD(WSLCE2E_Image_Build_Target_Success)
161 {
162 + auto imageCleanup = DeleteImageOnExit(BuiltImageTarget);
163 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-target";
164 auto cleanup = SetupTestDirectory(testRoot);
165
@@ -184,6 +196,7 @@ class WSLCE2EImageBuildTests
196
197 WSLC_TEST_METHOD(WSLCE2E_Image_Build_Label_Success)
198 {
199 + auto imageCleanup = DeleteImageOnExit(BuiltImageLabel);
200 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-label";
201 auto cleanup = SetupTestDirectory(testRoot);
202
@@ -219,6 +232,7 @@ class WSLCE2EImageBuildTests
232
233 WSLC_TEST_METHOD(WSLCE2E_Image_Build_LabelOverridesDockerfile_Success)
234 {
235 + auto imageCleanup = DeleteImageOnExit(BuiltImageLabelOverride);
236 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-label-override";
237 auto cleanup = SetupTestDirectory(testRoot);
238
@@ -249,11 +263,13 @@ class WSLCE2EImageBuildTests
263
264 WSLC_TEST_METHOD(WSLCE2E_Image_Build_DockerfileInContextDir_Success)
265 {
266 + auto imageCleanup = DeleteImageOnExit(BuiltImageDockerfile);
267 BuildFromContextFile(L"Dockerfile", BuiltImageDockerfile);
268 }
269
270 WSLC_TEST_METHOD(WSLCE2E_Image_Build_ContainerfileInContextDir_Success)
271 {
272 + auto imageCleanup = DeleteImageOnExit(BuiltImageContainerfile);
273 BuildFromContextFile(L"Containerfile", BuiltImageContainerfile);
274 }
275
@@ -307,6 +323,7 @@ class WSLCE2EImageBuildTests
323
324 WSLC_TEST_METHOD(WSLCE2E_Image_Build_NoCache_Success)
325 {
326 + auto imageCleanup = DeleteImageOnExit(BuiltImageNoCache);
327 auto testRoot = std::filesystem::current_path() / L"wslc-e2e-build-no-cache";
328 auto cleanup = SetupTestDirectory(testRoot);
329
@@ -372,19 +389,5 @@ private:
389 VERIFY_ARE_EQUAL(1u, inspectData.RepoTags.value().size());
390 VERIFY_ARE_EQUAL(image.NameAndTag(), wsl::shared::string::MultiByteToWide(inspectData.RepoTags.value()[0]));
391 }
375 -
376 - void DeleteAllBuiltImages()
377 - {
378 - EnsureImageIsDeleted(BuiltImage);
379 - EnsureImageIsDeleted(BuiltImageTag1);
380 - EnsureImageIsDeleted(BuiltImageTag2);
381 - EnsureImageIsDeleted(BuiltImagePull);
382 - EnsureImageIsDeleted(BuiltImageTarget);
383 - EnsureImageIsDeleted(BuiltImageDockerfile);
384 - EnsureImageIsDeleted(BuiltImageContainerfile);
385 - EnsureImageIsDeleted(BuiltImageNoCache);
386 - EnsureImageIsDeleted(BuiltImageLabel);
387 - EnsureImageIsDeleted(BuiltImageLabelOverride);
388 - }
392 };
393 } // namespace WSLCE2ETests