diff --git a/docs/api/create_docker_container.md b/docs/api/create_docker_container.md index 653d4eee6..496547dd4 100644 --- a/docs/api/create_docker_container.md +++ b/docs/api/create_docker_container.md @@ -2,6 +2,30 @@ Testcontainers' generic container support offers the greatest flexibility and makes it easy to use virtually any container image in the context of a temporary test environment. To interact or exchange data with a container, Testcontainers provides `ContainerBuilder` to configure and create the resource. +## Configure container image + +To specify the container image, use `WithImage(...)`. + +The simplest overload accepts a `string`: + +```csharp +_ = new ContainerBuilder() + .WithImage("postgres:15.1"); +``` + +For more advanced scenarios, `WithImage` also supports `IImage`, giving you more control over how the image is represented and its properties are resolved. + +If you need to target a specific platform, the `DockerImage` implementation provides an overload that lets you explicitly set the platform, such as `linux/amd64`. By default, the container runtime uses the platform that matches the container host. + +```csharp +_ = new ContainerBuilder() + .WithImage(new DockerImage("postgres:15.1", new Platform("linux/amd64"))); +``` + +!!!tip + + A specifier has the format `||/[/]`. The user can provide either the operating system or the architecture or both. For more details, see [containerd/platforms](https://github.com/containerd/platforms). + ## Configure container start Both `ENTRYPOINT` and `CMD` allows you to configure an executable and parameters, that a container runs at the start. By default, a container will run whatever `ENTRYPOINT` or `CMD` is specified in the Docker container image. At least one of both configurations is necessary. The container builder implementation supports `WithEntrypoint(params string[])` and `WithCommand(params string[])` to set or override the executable. Ideally, the `ENTRYPOINT` should set the container's executable, whereas the `CMD` sets the default arguments for the `ENTRYPOINT`. diff --git a/src/Testcontainers/Clients/DockerContainerOperations.cs b/src/Testcontainers/Clients/DockerContainerOperations.cs index efc521678..23aacf026 100644 --- a/src/Testcontainers/Clients/DockerContainerOperations.cs +++ b/src/Testcontainers/Clients/DockerContainerOperations.cs @@ -202,6 +202,7 @@ public async Task RunAsync(IContainerConfiguration configuration, Cancel var createParameters = new CreateContainerParameters { Image = configuration.Image.FullName, + Platform = configuration.Image.Platform, Name = configuration.Name, Hostname = configuration.Hostname, WorkingDir = configuration.WorkingDirectory, diff --git a/src/Testcontainers/Clients/DockerImageOperations.cs b/src/Testcontainers/Clients/DockerImageOperations.cs index 3fdad710c..53d04d77f 100644 --- a/src/Testcontainers/Clients/DockerImageOperations.cs +++ b/src/Testcontainers/Clients/DockerImageOperations.cs @@ -60,6 +60,7 @@ public async Task CreateAsync(IImage image, IDockerRegistryAuthenticationConfigu var createParameters = new ImagesCreateParameters { FromImage = image.FullName, + Platform = image.Platform, }; var authConfig = new AuthConfig diff --git a/src/Testcontainers/Images/DockerImage.cs b/src/Testcontainers/Images/DockerImage.cs index d41a4c1d7..ac2884d68 100644 --- a/src/Testcontainers/Images/DockerImage.cs +++ b/src/Testcontainers/Images/DockerImage.cs @@ -31,12 +31,15 @@ public sealed class DockerImage : IImage [CanBeNull] private readonly string _digest; + [CanBeNull] + private readonly string _platform; + /// /// Initializes a new instance of the class. /// /// The image. public DockerImage(IImage image) - : this(image.Repository, image.Registry, image.Tag, image.Digest) + : this(image.Repository, image.Registry, image.Tag, image.Digest, image.Platform) { } @@ -50,6 +53,20 @@ public DockerImage(string image) { } + /// + /// Initializes a new instance of the class. + /// + /// The image. + /// The platform. + /// fedora/httpd:version1.0 where fedora/httpd is the repository and version1.0 the tag. + public DockerImage( + string image, + Platform platform) + : this(GetDockerImage(image)) + { + _platform = platform.Value; + } + /// /// Initializes a new instance of the class. /// @@ -57,6 +74,7 @@ public DockerImage(string image) /// The registry. /// The tag. /// The digest. + /// The platform. /// The Docker Hub image name prefix. /// fedora/httpd:version1.0 where fedora/httpd is the repository and version1.0 the tag. public DockerImage( @@ -64,12 +82,14 @@ public DockerImage( string registry = null, string tag = null, string digest = null, + string platform = null, string hubImageNamePrefix = null) : this( TrimOrDefault(repository), TrimOrDefault(registry), TrimOrDefault(tag, tag == null && digest == null ? LatestTag : null), TrimOrDefault(digest), + TrimOrDefault(platform), hubImageNamePrefix == null ? [] : hubImageNamePrefix.Trim(TrimChars).Split(SlashChar, 2, StringSplitOptions.RemoveEmptyEntries)) { } @@ -79,6 +99,7 @@ private DockerImage( string registry, string tag, string digest, + string platform, string[] substitutions) { _ = Guard.Argument(repository, nameof(repository)) @@ -109,6 +130,7 @@ private DockerImage( _tag = tag; _digest = digest; + _platform = platform; } /// @@ -123,6 +145,9 @@ private DockerImage( /// public string Digest => _digest; + /// + public string Platform => _platform; + /// public string FullName { diff --git a/src/Testcontainers/Images/DockerfileArchive.cs b/src/Testcontainers/Images/DockerfileArchive.cs index e2f1e1e23..79261fa2b 100644 --- a/src/Testcontainers/Images/DockerfileArchive.cs +++ b/src/Testcontainers/Images/DockerfileArchive.cs @@ -123,12 +123,14 @@ private DockerfileArchive( /// An of . public IEnumerable GetBaseImages() { - const string imageGroup = "image"; - const string nameGroup = "name"; const string valueGroup = "value"; + const string argGroup = "arg"; + + const string imageGroup = "image"; + var lines = File.ReadAllLines(_dockerfile.FullName) .Select(line => line.Trim()) .Where(line => !string.IsNullOrEmpty(line)) @@ -160,13 +162,16 @@ public IEnumerable GetBaseImages() .ToArray(); var images = fromMatches - .Select(match => match.Groups[imageGroup]) - .Select(match => match.Value) - .Select(line => ReplaceVariables(line, args)) - .Where(line => !line.Any(char.IsUpper)) - .Where(value => !stages.Contains(value)) - .Distinct() - .Select(value => new DockerImage(value)) + .Select(match => (FromArgs: match.Groups[argGroup], Image: match.Groups[imageGroup])) + .Select(item => (FromArgs: ReplaceVariables(item.FromArgs.Value, args), Image: ReplaceVariables(item.Image.Value, args))) + .Where(item => !item.Image.Any(char.IsUpper)) + .Where(item => !stages.Contains(item.Image)) + .Select(item => + { + var fromArgs = ParseFromArgs(item.FromArgs).ToDictionary(arg => arg.Name, arg => arg.Value); + _ = fromArgs.TryGetValue("platform", out var platform); + return new DockerImage(item.Image, new Platform(platform)); + }) .ToArray(); return images; @@ -213,11 +218,11 @@ await AddAsync(absoluteFilePath, relativeFilePath, tarOutputStream) .ConfigureAwait(false); } - var dockerfileDirectoryLength = _dockerfileDirectory.FullName + var dockerfileDirectoryLength = _dockerfileDirectory.FullName .TrimEnd(Path.DirectorySeparatorChar).Length + 1; var dockerfileRelativeFilePath = _dockerfile.FullName - .Substring(dockerfileDirectoryLength ); + .Substring(dockerfileDirectoryLength); var dockerfileNormalizedRelativeFilePath = Unix.Instance.NormalizePath(dockerfileRelativeFilePath); @@ -306,23 +311,121 @@ private static int GetUnixFileMode(string filePath) /// corresponding build argument if present; otherwise, the default value in the /// Dockerfile is preserved. /// - /// The image string from a Dockerfile FROM statement. + /// The line from a Dockerfile FROM statement. /// A dictionary containing variable names as keys and their replacement values as values. /// A new image string where placeholders are replaced with their corresponding values. - private static string ReplaceVariables(string image, IDictionary variables) + private static string ReplaceVariables(string line, IDictionary variables) { const string nameGroup = "name"; if (variables.Count == 0) { - return image; + return line; } - return VariablePattern.Replace(image, match => + return VariablePattern.Replace(line, match => { var name = match.Groups[nameGroup].Value; return variables.TryGetValue(name, out var value) ? value : match.Value; }); } + + /// + /// Parses a FROM statement arg string into flag and value pairs. + /// + /// + /// This method parses a string containing FROM statement style flags, + /// respecting quoted values. Both double quotes (") and single + /// quotes (') are supported. Whitespaces outside of quotes are + /// treated as separators. + /// + /// E.g., the line --pull=always --platform="linux/amd64" becomes: + /// + /// + /// + /// + /// (pull, always) + /// + /// + /// + /// + /// (platform, linux/amd64) + /// + /// + /// + /// + /// + /// The FROM statement arg string containing flags and optional values. + /// + /// + /// A sequence of (Name, Value) tuples. + /// + /// + /// Thrown if a quoted value is missing a closing quote. + /// + private static IEnumerable<(string Name, string Value)> ParseFromArgs(string line) + { + if (string.IsNullOrEmpty(line)) + { + yield break; + } + + char? quote = null; + + var start = 0; + + for (var i = 0; i < line.Length; i++) + { + var c = line[i]; + + if ((c == '"' || c == '\'') && (quote == null || quote == c)) + { + quote = quote == null ? c : null; + } + + if (quote != null || !char.IsWhiteSpace(c)) + { + continue; + } + + if (i > start) + { + yield return ParseArg(line.Substring(start, i - start)); + } + + start = i + 1; + } + + if (quote != null) + { + throw new FormatException($"Unmatched {quote} quote in line: '{line}'."); + } + + if (line.Length > start) + { + yield return ParseArg(line.Substring(start)); + } + } + + /// + /// Splits a single arg into flag name and an optional value. + /// + /// A single arg, optionally containing an equals sign and value. + /// A tuple containing the flag name and its value, or null if no value is specified. + private static (string Name, string Value) ParseArg(string arg) + { + var trimmed = arg.TrimStart('-'); + var eqIndex = trimmed.IndexOf('='); + if (eqIndex == -1) + { + return (trimmed, null); + } + else + { + var name = trimmed.Substring(0, eqIndex); + var value = trimmed.Substring(eqIndex + 1).Trim().Trim('"', '\''); + return (name, value); + } + } } } diff --git a/src/Testcontainers/Images/FutureDockerImage.cs b/src/Testcontainers/Images/FutureDockerImage.cs index 32a44ee96..6c2f7dab4 100644 --- a/src/Testcontainers/Images/FutureDockerImage.cs +++ b/src/Testcontainers/Images/FutureDockerImage.cs @@ -68,6 +68,16 @@ public string Digest } } + /// + public string Platform + { + get + { + ThrowIfResourceNotFound(); + return _configuration.Image.Platform; + } + } + /// public string FullName { diff --git a/src/Testcontainers/Images/IImage.cs b/src/Testcontainers/Images/IImage.cs index 8f35495f7..ec0083f08 100644 --- a/src/Testcontainers/Images/IImage.cs +++ b/src/Testcontainers/Images/IImage.cs @@ -33,6 +33,16 @@ public interface IImage [CanBeNull] string Digest { get; } + /// + /// Gets the platform. + /// + /// + /// The supported format for a platform value is: + /// <os>|<arch>|<os>/<arch>[/<variant>]. + /// + [CanBeNull] + string Platform { get; } + /// /// Gets the full image name. /// diff --git a/src/Testcontainers/Images/IImageExtensions.cs b/src/Testcontainers/Images/IImageExtensions.cs index 55067ae80..b31c8e013 100644 --- a/src/Testcontainers/Images/IImageExtensions.cs +++ b/src/Testcontainers/Images/IImageExtensions.cs @@ -27,7 +27,7 @@ public static IImage ApplyHubImageNamePrefix(this IImage image) return image; } - return new DockerImage(image.Repository, image.Registry, image.Tag, image.Digest, TestcontainersSettings.HubImageNamePrefix); + return new DockerImage(image.Repository, image.Registry, image.Tag, image.Digest, image.Platform, TestcontainersSettings.HubImageNamePrefix); } } } diff --git a/src/Testcontainers/Images/Platform.cs b/src/Testcontainers/Images/Platform.cs new file mode 100644 index 000000000..ace0ea4ef --- /dev/null +++ b/src/Testcontainers/Images/Platform.cs @@ -0,0 +1,39 @@ +namespace DotNet.Testcontainers.Images +{ + using JetBrains.Annotations; + + /// + /// Represents a container platform identifier. + /// + /// + /// The supported format for a platform value is: + /// <os>|<arch>|<os>/<arch>[/<variant>]. + /// + /// You can provide either the operating system or the architecture or both. + /// For more details, see containerd/platforms. + /// + [PublicAPI] + public readonly struct Platform + { + /// + /// Initializes a new instance of the struct. + /// + /// The platform identifier. + [PublicAPI] + public Platform(string value) + { + Value = value; + } + + /// + /// Gets the platform identifier. + /// + /// + /// A string representing the container platform in containerd/platforms format, or + /// null if no platform was specified. + /// + [PublicAPI] + [CanBeNull] + public string Value { get; } + } +} diff --git a/tests/Testcontainers.Tests/Assets/pullBaseImages/Dockerfile b/tests/Testcontainers.Tests/Assets/pullBaseImages/Dockerfile index ff431cd45..6d04bc65e 100644 --- a/tests/Testcontainers.Tests/Assets/pullBaseImages/Dockerfile +++ b/tests/Testcontainers.Tests/Assets/pullBaseImages/Dockerfile @@ -1,4 +1,6 @@ ARG REPO=mcr.microsoft.com/dotnet/aspnet +ARG PLATFORM=linux/arm64 + FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime FROM build @@ -8,7 +10,10 @@ FROM ${REPO}:8.0-noble FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine # https://github.com/testcontainers/testcontainers-dotnet/issues/993. -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0 +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0 +FROM --platform=$PLATFORM mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0 +FROM --platform="linux/arm/v6" mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0 +FROM --platform='linux/arm/v7' mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0 # https://github.com/testcontainers/testcontainers-dotnet/issues/1030. FROM mcr.microsoft.com/dotnet/sdk:$SDK_VERSION_8_0 AS build_sdk_8_0 diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerMTls.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerMTls.cs index 8921710df..44dd34268 100644 --- a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerMTls.cs +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerMTls.cs @@ -2,7 +2,6 @@ namespace DotNet.Testcontainers.Tests.Fixtures { using System.Collections.Generic; using DotNet.Testcontainers.Builders; - using DotNet.Testcontainers.Images; public abstract class DockerMTls : ProtectDockerDaemonSocket { diff --git a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs index aee4d2981..1693aa47e 100644 --- a/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Containers/Unix/DockerTlsFixture.cs @@ -2,7 +2,6 @@ namespace DotNet.Testcontainers.Tests.Fixtures { using System.Collections.Generic; using DotNet.Testcontainers.Builders; - using DotNet.Testcontainers.Images; using JetBrains.Annotations; [UsedImplicitly] diff --git a/tests/Testcontainers.Tests/Fixtures/Images/DockerImageFixture.cs b/tests/Testcontainers.Tests/Fixtures/Images/DockerImageFixture.cs index 4497fd8fd..e4a37ea45 100644 --- a/tests/Testcontainers.Tests/Fixtures/Images/DockerImageFixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Images/DockerImageFixture.cs @@ -37,8 +37,8 @@ public DockerImageFixture() Add(new DockerImageFixtureSerializable(new DockerImage(FedoraHttpd, PortSeparatorRegistry, CustomTag1, null)), $"{PortSeparatorRegistry}/{FedoraHttpd}:{CustomTag1}", $"{PortSeparatorRegistry}/{FedoraHttpd}:{CustomTag1}"); Add(new DockerImageFixtureSerializable(new DockerImage(FooBarBaz, DotSeparatorRegistry, SemVerTag, Digest)), $"{DotSeparatorRegistry}/{FooBarBaz}:{SemVerTag}@{Digest}", $"{DotSeparatorRegistry}/{FooBarBaz}:{SemVerTag}@{Digest}"); Add(new DockerImageFixtureSerializable(new DockerImage(FooBarBaz, DotSeparatorRegistry, null, Digest)), $"{DotSeparatorRegistry}/{FooBarBaz}@{Digest}", $"{DotSeparatorRegistry}/{FooBarBaz}@{Digest}"); - Add(new DockerImageFixtureSerializable(new DockerImage(BarBaz, null, null, null, HubImageNamePrefixImplicitLibrary)), $"{HubImageNamePrefixImplicitLibrary}/{BarBaz}", $"{HubImageNamePrefixImplicitLibrary}/{BarBaz}:{LatestTag}"); - Add(new DockerImageFixtureSerializable(new DockerImage(BarBaz, null, null, null, HubImageNamePrefixExplicitLibrary)), $"{HubImageNamePrefixExplicitLibrary}/{BarBaz}", $"{HubImageNamePrefixExplicitLibrary}/{BarBaz}:{LatestTag}"); + Add(new DockerImageFixtureSerializable(new DockerImage(BarBaz, null, null, null, null, HubImageNamePrefixImplicitLibrary)), $"{HubImageNamePrefixImplicitLibrary}/{BarBaz}", $"{HubImageNamePrefixImplicitLibrary}/{BarBaz}:{LatestTag}"); + Add(new DockerImageFixtureSerializable(new DockerImage(BarBaz, null, null, null, null, HubImageNamePrefixExplicitLibrary)), $"{HubImageNamePrefixExplicitLibrary}/{BarBaz}", $"{HubImageNamePrefixExplicitLibrary}/{BarBaz}:{LatestTag}"); } } } diff --git a/tests/Testcontainers.Tests/Fixtures/Images/DockerImageFixtureSerializable.cs b/tests/Testcontainers.Tests/Fixtures/Images/DockerImageFixtureSerializable.cs index ebe799359..2176f5a5c 100644 --- a/tests/Testcontainers.Tests/Fixtures/Images/DockerImageFixtureSerializable.cs +++ b/tests/Testcontainers.Tests/Fixtures/Images/DockerImageFixtureSerializable.cs @@ -22,7 +22,8 @@ public void Deserialize(IXunitSerializationInfo info) var registry = info.GetValue("Registry"); var tag = info.GetValue("Tag"); var digest = info.GetValue("Digest"); - Image = new DockerImage(repository, registry, tag, digest); + var platform = info.GetValue("Platform"); + Image = new DockerImage(repository, registry, tag, digest, platform); } public void Serialize(IXunitSerializationInfo info) @@ -31,6 +32,7 @@ public void Serialize(IXunitSerializationInfo info) info.AddValue("Registry", Image.Registry); info.AddValue("Tag", Image.Tag); info.AddValue("Digest", Image.Digest); + info.AddValue("Platform", Image.Platform); } } } diff --git a/tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs b/tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs index d99399573..f27984fa6 100644 --- a/tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs +++ b/tests/Testcontainers.Tests/Fixtures/Images/HealthCheckFixture.cs @@ -23,6 +23,8 @@ public sealed class HealthCheckFixture : IImage, IAsyncLifetime public string Digest => _image.Digest; + public string Platform => _image.Platform; + public string FullName => _image.FullName; public string GetHostname() diff --git a/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs index 855d190c0..51dbc0358 100644 --- a/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs +++ b/tests/Testcontainers.Tests/Unit/Configurations/DockerRegistryAuthenticationProviderTest.cs @@ -48,7 +48,7 @@ public void GetHostnameFromDockerImage(string dockerImageName, string hostname) public void GetHostnameFromHubImageNamePrefix(string repository, string tag) { const string hubImageNamePrefix = "myregistry.azurecr.io"; - IImage image = new DockerImage(repository, null, tag, null, hubImageNamePrefix); + IImage image = new DockerImage(repository, null, tag, null, null, hubImageNamePrefix); Assert.Equal(hubImageNamePrefix, image.GetHostname()); } diff --git a/tests/Testcontainers.Tests/Unit/Images/ImageFromDockerfileTest.cs b/tests/Testcontainers.Tests/Unit/Images/ImageFromDockerfileTest.cs index 82c4d4ef8..95c55f961 100644 --- a/tests/Testcontainers.Tests/Unit/Images/ImageFromDockerfileTest.cs +++ b/tests/Testcontainers.Tests/Unit/Images/ImageFromDockerfileTest.cs @@ -4,7 +4,6 @@ namespace DotNet.Testcontainers.Tests.Unit using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; - using System.Linq; using System.Text; using System.Threading.Tasks; using DotNet.Testcontainers.Builders; @@ -23,13 +22,16 @@ public void DockerfileArchiveGetBaseImages() // Given var expected = new[] { - "mcr.microsoft.com/dotnet/sdk:8.0", - "mcr.microsoft.com/dotnet/runtime:8.0", - "mcr.microsoft.com/dotnet/aspnet:8.0-jammy", - "mcr.microsoft.com/dotnet/aspnet:8.0-noble", - "mcr.microsoft.com/dotnet/aspnet:8.0-alpine", - "mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0", - "mcr.microsoft.com/dotnet/sdk:8.0.414", + new DockerImage("mcr.microsoft.com/dotnet/sdk:8.0"), + new DockerImage("mcr.microsoft.com/dotnet/runtime:8.0"), + new DockerImage("mcr.microsoft.com/dotnet/aspnet:8.0-jammy"), + new DockerImage("mcr.microsoft.com/dotnet/aspnet:8.0-noble"), + new DockerImage("mcr.microsoft.com/dotnet/aspnet:8.0-alpine"), + new DockerImage("mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0", new Platform("linux/amd64")), + new DockerImage("mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0", new Platform("linux/arm64")), + new DockerImage("mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0", new Platform("linux/arm/v6")), + new DockerImage("mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0", new Platform("linux/arm/v7")), + new DockerImage("mcr.microsoft.com/dotnet/sdk:8.0.414"), }; IImage image = new DockerImage("localhost/testcontainers", Guid.NewGuid().ToString("D"), string.Empty); @@ -44,7 +46,7 @@ public void DockerfileArchiveGetBaseImages() var actual = dockerfileArchive.GetBaseImages(); // Then - Assert.Equal(expected, actual.Select(baseImage => baseImage.FullName)); + Assert.Equivalent(expected, actual); } [Fact] diff --git a/tests/Testcontainers.Tests/Unit/Images/TestcontainersImageTest.cs b/tests/Testcontainers.Tests/Unit/Images/TestcontainersImageTest.cs index 4dc349470..cf1cd9e45 100644 --- a/tests/Testcontainers.Tests/Unit/Images/TestcontainersImageTest.cs +++ b/tests/Testcontainers.Tests/Unit/Images/TestcontainersImageTest.cs @@ -69,6 +69,36 @@ public void WhenImageNameGetsAssigned(DockerImageFixtureSerializable serializabl Assert.Equal(fullName, actual.FullName); } + [Fact] + public void Platform_NoPlatformSpecified_ReturnsNull() + { + // Given + IImage dockerImage = new DockerImage("foo"); + + // When + var result = dockerImage.Platform; + + // Then + Assert.Null(result); + } + + [Theory] + [InlineData("linux/amd64")] + [InlineData("linux/arm64")] + [InlineData("linux/arm/v6")] + [InlineData("linux/arm/v7")] + public void Platform_PlatformSpecified_ReturnsPlatform(string platform) + { + // Given + IImage dockerImage = new DockerImage("foo", new Platform(platform)); + + // When + var result = dockerImage.Platform; + + // Then + Assert.Equal(platform, result); + } + [Fact] public void MatchLatestOrNightly_TagIsLatest_ReturnsTrue() {