Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions tests/Microsoft.DotNet.Docker.Tests/DockerImageTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using Xunit.Abstractions;

namespace Microsoft.DotNet.Docker.Tests;

/// <summary>
/// Base class for tests that build Docker images. Tracks all images created
/// during test execution and automatically cleans them up after each test
/// completes. Each individual test method will have its images cleaned up
/// after test completion.
/// </summary>
public abstract class DockerImageTestBase : IDisposable
{
private readonly List<string> _createdImageTags = [];

protected ITestOutputHelper OutputHelper { get; }
protected DockerHelper DockerHelper { get; }

protected DockerImageTestBase(ITestOutputHelper outputHelper)
{
OutputHelper = outputHelper;
DockerHelper = new DockerHelper(outputHelper);
}

public void Dispose()
{
if (_createdImageTags.Count > 0)
{
OutputHelper.WriteLine($"Cleaning up {_createdImageTags.Count} image(s) created during test:");
foreach (string tag in _createdImageTags)
{
OutputHelper.WriteLine($" - {tag}");
}

foreach (string tag in _createdImageTags)
{
try
{
DockerHelper.DeleteImage(tag);
}
catch
{
// Best effort cleanup - don't fail the test if image deletion fails
OutputHelper.WriteLine($"Warning: Failed to delete image '{tag}'");
}
}

_createdImageTags.Clear();
}

GC.SuppressFinalize(this);
}

/// <summary>
/// Builds a Docker image and registers it for cleanup after the test completes.
/// </summary>
protected void Build(
string tag,
string dockerfile = "",
string target = "",
string contextDir = ".",
bool pull = false,
string platform = "",
string output = "",
params string[] buildArgs)
{
DockerHelper.Build(tag, dockerfile, target, contextDir, pull, platform, output, buildArgs);
if (!string.IsNullOrEmpty(tag))
{
_createdImageTags.Add(tag);
}
}

/// <summary>
/// Builds a helper image intended to test distroless scenarios.
/// </summary>
/// <remarks>
/// Because distroless containers do not contain a shell, and potentially other packages necessary for testing,
/// this helper image stores the entire root of the distroless filesystem at the specified destination path within
/// the built container image.
/// </remarks>
protected string BuildDistrolessHelper(DotNetImageRepo imageRepo, ProductImageData imageData, string copyDestination, string copyOrigin = "/")
{
string tag = DockerHelper.BuildDistrolessHelper(imageRepo, imageData, copyDestination, copyOrigin);
_createdImageTags.Add(tag);
return tag;
}
}
12 changes: 4 additions & 8 deletions tests/Microsoft.DotNet.Docker.Tests/ProductImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,13 @@

namespace Microsoft.DotNet.Docker.Tests
{
public abstract class ProductImageTests
public abstract class ProductImageTests : DockerImageTestBase
{
protected ProductImageTests(ITestOutputHelper outputHelper)
protected ProductImageTests(ITestOutputHelper outputHelper) : base(outputHelper)
{
DockerHelper = new DockerHelper(outputHelper);
OutputHelper = outputHelper;
SyftHelper = new SyftHelper(DockerHelper, OutputHelper);
}

protected DockerHelper DockerHelper { get; }
protected ITestOutputHelper OutputHelper { get; }
protected SyftHelper SyftHelper { get; }
protected abstract DotNetImageRepo ImageRepo { get; }

Expand Down Expand Up @@ -67,7 +63,7 @@ protected void VerifyCommonInsecureFiles(ProductImageData imageData)
string imageTag;
if (imageData.IsDistroless)
{
imageTag = DockerHelper.BuildDistrolessHelper(ImageRepo, imageData, rootFsPath);
imageTag = BuildDistrolessHelper(ImageRepo, imageData, rootFsPath);
}
else
{
Expand Down Expand Up @@ -148,7 +144,7 @@ protected void VerifyNonRootUID(ProductImageData imageData)
if (imageData.IsDistroless)
{
rootPath = "/rootfs/";
imageTag = DockerHelper.BuildDistrolessHelper(ImageRepo, imageData, rootPath);
imageTag = BuildDistrolessHelper(ImageRepo, imageData, rootPath);
}

string command = $"-c \"grep '^app' {rootPath}etc/passwd | cut -d: -f3\"";
Expand Down
45 changes: 15 additions & 30 deletions tests/Microsoft.DotNet.Docker.Tests/SampleImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,15 @@
namespace Microsoft.DotNet.Docker.Tests
{
[Trait("Category", "sample")]
public class SampleImageTests
public class SampleImageTests : DockerImageTestBase
{
private static readonly string s_samplesPath = Path.Combine(Config.SourceRepoRoot, "samples");

public SampleImageTests(ITestOutputHelper outputHelper)
: base(outputHelper)
{
OutputHelper = outputHelper;
DockerHelper = new DockerHelper(outputHelper);
}

protected DockerHelper DockerHelper { get; }

protected ITestOutputHelper OutputHelper { get; }

public static IEnumerable<object[]> GetImageData()
{
return TestData.GetSampleImageData()
Expand Down Expand Up @@ -103,13 +98,13 @@ public void VerifyComplexAppSample()
try
{
// Test that the app works
DockerHelper.Build(appTag, dockerfilePath, contextDir: sampleFolder, pull: Config.PullImages);
Build(appTag, dockerfilePath, contextDir: sampleFolder, pull: Config.PullImages);
string containerName = ImageData.GenerateContainerName("sample-complex");
string output = DockerHelper.Run(appTag, containerName);
Assert.StartsWith("string: The quick brown fox jumps over the lazy dog", output);

// Run the app's tests
DockerHelper.Build(testTag, dockerfilePath, target: "test", contextDir: sampleFolder);
Build(testTag, dockerfilePath, target: "test", contextDir: sampleFolder);
DockerHelper.Run(testTag, testContainerName, skipAutoCleanup: true);

// Copy the test log from the container to the host
Expand All @@ -134,8 +129,7 @@ public void VerifyComplexAppSample()
}

DockerHelper.DeleteContainer(testContainerName);
DockerHelper.DeleteImage(testTag);
DockerHelper.DeleteImage(appTag);
// Images are now cleaned up automatically by the base class
}
}

Expand All @@ -146,30 +140,21 @@ private async Task VerifySampleAsync(
{
string image = imageData.GetImage(sampleImageType, DockerHelper);
string imageType = Enum.GetName(typeof(SampleImageType), sampleImageType).ToLowerInvariant();
try
{
if (!imageData.IsPublished)
{
string sampleFolder = Path.Combine(s_samplesPath, imageType);
string dockerfilePath = $"{sampleFolder}/Dockerfile";
if (!string.IsNullOrEmpty(imageData.DockerfileSuffix))
{
dockerfilePath += $".{imageData.DockerfileSuffix}";
}

DockerHelper.Build(image, dockerfilePath, contextDir: sampleFolder, pull: Config.PullImages, platform: imageData.Platform);
}

string containerName = imageData.GetIdentifier($"sample-{imageType}");
await verifyImageAsync(image, containerName);
}
finally
if (!imageData.IsPublished)
{
if (!imageData.IsPublished)
string sampleFolder = Path.Combine(s_samplesPath, imageType);
string dockerfilePath = $"{sampleFolder}/Dockerfile";
if (!string.IsNullOrEmpty(imageData.DockerfileSuffix))
{
DockerHelper.DeleteImage(image);
dockerfilePath += $".{imageData.DockerfileSuffix}";
}

Build(image, dockerfilePath, contextDir: sampleFolder, pull: Config.PullImages, platform: imageData.Platform);
}

string containerName = imageData.GetIdentifier($"sample-{imageType}");
await verifyImageAsync(image, containerName);
}

private void ValidateEnvironmentVariables(SampleImageData imageData, string image, SampleImageType imageType)
Expand Down
3 changes: 1 addition & 2 deletions tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,7 @@ private IEnumerable<SdkContentFileInfo> GetActualSdkContents(ProductImageData im
string baseImage = imageData.GetImage(ImageRepo, DockerHelper);
string tag = imageData.GetIdentifier("SdkContents").ToLower();

DockerHelper.Build(
tag: tag,
Build(tag: tag,
dockerfile: Path.Combine(DockerHelper.TestArtifactsDir, "Dockerfile.copy"),
contextDir: DockerHelper.TestArtifactsDir,
platform: imageData.Platform,
Expand Down