diff --git a/src/Testcontainers/Builders/ContainerBuilder`3.cs b/src/Testcontainers/Builders/ContainerBuilder`3.cs
index b1c48147a..32957700c 100644
--- a/src/Testcontainers/Builders/ContainerBuilder`3.cs
+++ b/src/Testcontainers/Builders/ContainerBuilder`3.cs
@@ -9,6 +9,7 @@ namespace DotNet.Testcontainers.Builders
using System.Threading.Tasks;
using Docker.DotNet.Models;
using DotNet.Testcontainers.Configurations;
+ using DotNet.Testcontainers.Configurations.Containers;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using DotNet.Testcontainers.Networks;
@@ -271,6 +272,13 @@ public TBuilderEntity WithResourceMapping(Uri source, string target, uint uid =
}
}
+ ///
+ public TBuilderEntity WithCopyTarArchive(Stream tarArchive, string containerPath = "/")
+ {
+ var tarArchiveMappings = new[] { new TarArchiveMapping(tarArchive, containerPath) };
+ return Clone(new ContainerConfiguration(tarArchiveMappings: tarArchiveMappings));
+ }
+
///
public TBuilderEntity WithMount(IMount mount)
{
diff --git a/src/Testcontainers/Builders/IContainerBuilder`2.cs b/src/Testcontainers/Builders/IContainerBuilder`2.cs
index 71b3c9c5b..8747f8f74 100644
--- a/src/Testcontainers/Builders/IContainerBuilder`2.cs
+++ b/src/Testcontainers/Builders/IContainerBuilder`2.cs
@@ -34,6 +34,7 @@ public interface IContainerBuilderA boolean value indicating whether the license agreement is accepted.
/// A configured instance of .
/// Thrown when the module does not require a license agreement.
+ [PublicAPI]
TBuilderEntity WithAcceptLicenseAgreement(bool acceptLicenseAgreement);
///
@@ -321,8 +322,27 @@ public interface IContainerBuilderThe group ID to set for the copied file or directory. Defaults to 0 (root).
/// The POSIX file mode permission.
/// A configured instance of .
+ [PublicAPI]
TBuilderEntity WithResourceMapping(Uri source, string target, uint uid = 0, uint gid = 0, UnixFileModes fileMode = Unix.FileMode644);
+ ///
+ /// Copies a tar archive contents to the container before it starts.
+ ///
+ ///
+ ///
+ /// Set the property to 0 before calling this method.
+ ///
+ ///
+ /// The caller retains ownership of the stream and is responsible for disposal.
+ /// The stream content is copied during container startup, so the stream must remain open and readable until the container starts.
+ ///
+ ///
+ /// The with the tar archive contents.
+ /// The path where tar archive contents should be placed.
+ /// A configured instance of .
+ [PublicAPI]
+ TBuilderEntity WithCopyTarArchive(Stream tarArchive, string containerPath = "/");
+
///
/// Assigns the mount configuration to manage data in the container.
///
diff --git a/src/Testcontainers/Clients/DockerContainerOperations.cs b/src/Testcontainers/Clients/DockerContainerOperations.cs
index 23aacf026..d8f363b5e 100644
--- a/src/Testcontainers/Clients/DockerContainerOperations.cs
+++ b/src/Testcontainers/Clients/DockerContainerOperations.cs
@@ -1,16 +1,16 @@
namespace DotNet.Testcontainers.Clients
{
+ using Docker.DotNet;
+ using Docker.DotNet.Models;
+ using DotNet.Testcontainers.Configurations;
+ using DotNet.Testcontainers.Containers;
+ using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
- using Docker.DotNet;
- using Docker.DotNet.Models;
- using DotNet.Testcontainers.Configurations;
- using DotNet.Testcontainers.Containers;
- using Microsoft.Extensions.Logging;
internal sealed class DockerContainerOperations : DockerApiClient, IDockerContainerOperations
{
@@ -109,9 +109,9 @@ public Task RemoveAsync(string id, CancellationToken ct = default)
return DockerClient.Containers.RemoveContainerAsync(id, new ContainerRemoveParameters { Force = true, RemoveVolumes = true }, ct);
}
- public Task ExtractArchiveToContainerAsync(string id, string path, TarOutputMemoryStream tarStream, CancellationToken ct = default)
+ public Task ExtractArchiveToContainerAsync(string id, string path, Stream tarStream, CancellationToken ct = default)
{
- Logger.CopyArchiveToDockerContainer(id, tarStream.ContentLength);
+ Logger.CopyArchiveToDockerContainer(id, tarStream.Length);
var copyToContainerParameters = new CopyToContainerParameters
{
diff --git a/src/Testcontainers/Clients/IDockerContainerOperations.cs b/src/Testcontainers/Clients/IDockerContainerOperations.cs
index a7f98d530..c7f4497e3 100644
--- a/src/Testcontainers/Clients/IDockerContainerOperations.cs
+++ b/src/Testcontainers/Clients/IDockerContainerOperations.cs
@@ -1,13 +1,13 @@
namespace DotNet.Testcontainers.Clients
{
+ using Docker.DotNet.Models;
+ using DotNet.Testcontainers.Configurations;
+ using DotNet.Testcontainers.Containers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
- using Docker.DotNet.Models;
- using DotNet.Testcontainers.Configurations;
- using DotNet.Testcontainers.Containers;
internal interface IDockerContainerOperations : IHasListOperations
{
@@ -25,7 +25,7 @@ internal interface IDockerContainerOperations : IHasListOperations GetArchiveFromContainerAsync(string id, string path, CancellationToken ct = default);
diff --git a/src/Testcontainers/Clients/ITestcontainersClient.cs b/src/Testcontainers/Clients/ITestcontainersClient.cs
index 76be7bf47..cf31d0fe4 100644
--- a/src/Testcontainers/Clients/ITestcontainersClient.cs
+++ b/src/Testcontainers/Clients/ITestcontainersClient.cs
@@ -155,6 +155,25 @@ internal interface ITestcontainersClient
/// A task that completes when the file has been copied.
Task CopyAsync(string id, FileInfo source, string target, uint uid, uint gid, UnixFileModes fileMode, CancellationToken ct = default);
+ ///
+ /// Copies a tar archive contents to the container.
+ ///
+ ///
+ ///
+ /// Set the property to 0 before calling this method.
+ ///
+ ///
+ /// The caller retains ownership of the stream and is responsible for disposal.
+ /// The stream content is copied during container startup, so the stream must remain open and readable until the container starts.
+ ///
+ ///
+ /// The container id.
+ /// The with the tar archive contents.
+ /// The path where tar archive contents should be placed.
+ /// Cancellation token.
+ /// A task that completes when the tar archive has been copied.
+ Task CopyTarArchiveAsync(string id, Stream tarArchive, string containerPath = "/", CancellationToken ct = default);
+
///
/// Reads a file from the container.
///
diff --git a/src/Testcontainers/Clients/TestcontainersClient.cs b/src/Testcontainers/Clients/TestcontainersClient.cs
index c7ddeb8ca..adfd3d7a7 100644
--- a/src/Testcontainers/Clients/TestcontainersClient.cs
+++ b/src/Testcontainers/Clients/TestcontainersClient.cs
@@ -263,6 +263,13 @@ await Container.ExtractArchiveToContainerAsync(id, "/", tarOutputMemStream, ct)
}
}
+ ///
+ public async Task CopyTarArchiveAsync(string id, Stream tarArchive, string containerPath = "/", CancellationToken ct = default)
+ {
+ await Container.ExtractArchiveToContainerAsync(id, containerPath, tarArchive, ct)
+ .ConfigureAwait(false);
+ }
+
///
public async Task ReadFileAsync(string id, string filePath, CancellationToken ct = default)
{
@@ -353,6 +360,12 @@ await Task.WhenAll(configuration.ResourceMappings.Select(resourceMapping => Copy
.ConfigureAwait(false);
}
+ if (configuration.TarArchiveMappings.Any())
+ {
+ await Task.WhenAll(configuration.TarArchiveMappings.Select(tarArchive => CopyTarArchiveAsync(id, tarArchive.TarArchive, tarArchive.ContainerPath, ct)))
+ .ConfigureAwait(false);
+ }
+
return id;
}
diff --git a/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs b/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs
index a56845e42..f7a6ed87f 100644
--- a/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs
+++ b/src/Testcontainers/Configurations/Containers/ContainerConfiguration.cs
@@ -1,17 +1,18 @@
+using Docker.DotNet.Models;
+using DotNet.Testcontainers.Builders;
+using DotNet.Testcontainers.Configurations.Containers;
+using DotNet.Testcontainers.Containers;
+using DotNet.Testcontainers.Images;
+using DotNet.Testcontainers.Networks;
+using JetBrains.Annotations;
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+
namespace DotNet.Testcontainers.Configurations
{
- using System;
- using System.Collections.Generic;
- using System.Text.Json.Serialization;
- using System.Threading;
- using System.Threading.Tasks;
- using Docker.DotNet.Models;
- using DotNet.Testcontainers.Builders;
- using DotNet.Testcontainers.Containers;
- using DotNet.Testcontainers.Images;
- using DotNet.Testcontainers.Networks;
- using JetBrains.Annotations;
-
///
[PublicAPI]
public class ContainerConfiguration : ResourceConfiguration, IContainerConfiguration
@@ -42,6 +43,7 @@ public class ContainerConfiguration : ResourceConfigurationThe connection string provider.
/// A value indicating whether Docker removes the container after it exits or not.
/// A value indicating whether the privileged flag is set or not.
+ /// A list of tar archive mappings.
public ContainerConfiguration(
IImage image = null,
Func imagePullPolicy = null,
@@ -65,7 +67,8 @@ public ContainerConfiguration(
Func startupCallback = null,
IConnectionStringProvider connectionStringProvider = null,
bool? autoRemove = null,
- bool? privileged = null)
+ bool? privileged = null,
+ IEnumerable tarArchiveMappings = null)
{
AutoRemove = autoRemove;
Privileged = privileged;
@@ -90,6 +93,7 @@ public ContainerConfiguration(
WaitStrategies = waitStrategies;
StartupCallback = startupCallback;
ConnectionStringProvider = connectionStringProvider;
+ TarArchiveMappings = tarArchiveMappings;
}
///
@@ -130,6 +134,7 @@ public ContainerConfiguration(IContainerConfiguration oldValue, IContainerConfig
ExposedPorts = BuildConfiguration.Combine(oldValue.ExposedPorts, newValue.ExposedPorts);
PortBindings = BuildConfiguration.Combine(oldValue.PortBindings, newValue.PortBindings);
ResourceMappings = BuildConfiguration.Combine(oldValue.ResourceMappings, newValue.ResourceMappings);
+ TarArchiveMappings = BuildConfiguration.Combine(oldValue.TarArchiveMappings, newValue.TarArchiveMappings);
Containers = BuildConfiguration.Combine(oldValue.Containers, newValue.Containers);
Mounts = BuildConfiguration.Combine(oldValue.Mounts, newValue.Mounts);
Networks = BuildConfiguration.Combine(oldValue.Networks, newValue.Networks);
@@ -192,6 +197,10 @@ public ContainerConfiguration(IContainerConfiguration oldValue, IContainerConfig
[JsonIgnore]
public IEnumerable ResourceMappings { get; }
+ ///
+ [JsonIgnore]
+ public IEnumerable TarArchiveMappings { get; }
+
///
[JsonIgnore]
public IEnumerable Containers { get; }
diff --git a/src/Testcontainers/Configurations/Containers/IContainerConfiguration.cs b/src/Testcontainers/Configurations/Containers/IContainerConfiguration.cs
index e4369fe07..e0accc2db 100644
--- a/src/Testcontainers/Configurations/Containers/IContainerConfiguration.cs
+++ b/src/Testcontainers/Configurations/Containers/IContainerConfiguration.cs
@@ -1,14 +1,16 @@
namespace DotNet.Testcontainers.Configurations
{
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
using Docker.DotNet.Models;
+ using DotNet.Testcontainers.Configurations.Containers;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
using DotNet.Testcontainers.Networks;
using JetBrains.Annotations;
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Threading;
+ using System.Threading.Tasks;
///
/// A container configuration.
@@ -86,6 +88,11 @@ public interface IContainerConfiguration : IResourceConfiguration
IEnumerable ResourceMappings { get; }
+ ///
+ /// Gets a list of tar archive mappings.
+ ///
+ IEnumerable TarArchiveMappings { get; }
+
///
/// Gets a list of dependent containers.
///
diff --git a/src/Testcontainers/Configurations/Containers/TarArchiveMapping.cs b/src/Testcontainers/Configurations/Containers/TarArchiveMapping.cs
new file mode 100644
index 000000000..e4f853fc9
--- /dev/null
+++ b/src/Testcontainers/Configurations/Containers/TarArchiveMapping.cs
@@ -0,0 +1,17 @@
+using System.IO;
+
+namespace DotNet.Testcontainers.Configurations.Containers
+{
+ public sealed record TarArchiveMapping
+ {
+ public TarArchiveMapping(Stream tarArchive, string containerPath)
+ {
+ TarArchive = tarArchive;
+ ContainerPath = containerPath;
+ }
+
+ public Stream TarArchive { get; }
+
+ public string ContainerPath { get; }
+ }
+}
diff --git a/src/Testcontainers/Containers/DockerContainer.cs b/src/Testcontainers/Containers/DockerContainer.cs
index 510e66a5e..8401d502c 100644
--- a/src/Testcontainers/Containers/DockerContainer.cs
+++ b/src/Testcontainers/Containers/DockerContainer.cs
@@ -152,28 +152,28 @@ public string Hostname
case "http":
case "https":
case "tcp":
- {
- return dockerEndpoint.Host;
- }
+ {
+ return dockerEndpoint.Host;
+ }
case "npipe":
case "unix":
- {
- const string localhost = "127.0.0.1";
-
- if (!Exists())
{
- return localhost;
- }
+ const string localhost = "127.0.0.1";
- if (!_client.IsRunningInsideDocker)
- {
- return localhost;
- }
+ if (!Exists())
+ {
+ return localhost;
+ }
+
+ if (!_client.IsRunningInsideDocker)
+ {
+ return localhost;
+ }
- var endpointSettings = _container.NetworkSettings.Networks.First().Value;
- return endpointSettings.Gateway;
- }
+ var endpointSettings = _container.NetworkSettings.Networks.First().Value;
+ return endpointSettings.Gateway;
+ }
default:
throw new InvalidOperationException($"Docker endpoint {dockerEndpoint} is not supported.");
@@ -406,6 +406,12 @@ public Task CopyAsync(DirectoryInfo source, string target, uint uid = 0, uint gi
return _client.CopyAsync(Id, source, target, uid, gid, fileMode, ct);
}
+ ///
+ public Task CopyTarArchiveAsync(Stream tarArchive, string containerPath = "/", CancellationToken ct = default)
+ {
+ return _client.CopyTarArchiveAsync(Id, tarArchive, containerPath, ct);
+ }
+
///
public Task ReadFileAsync(string filePath, CancellationToken ct = default)
{
diff --git a/src/Testcontainers/Containers/IContainer.cs b/src/Testcontainers/Containers/IContainer.cs
index 04eb6a5a7..2fa936dec 100644
--- a/src/Testcontainers/Containers/IContainer.cs
+++ b/src/Testcontainers/Containers/IContainer.cs
@@ -300,6 +300,24 @@ public interface IContainer : IConnectionStringProvider, IAsyncDisposable
/// A task that completes when the file has been copied.
Task CopyAsync(FileInfo source, string target, uint uid = 0, uint gid = 0, UnixFileModes fileMode = Unix.FileMode644, CancellationToken ct = default);
+ ///
+ /// Copies a tar archive contents to the container.
+ ///
+ ///
+ ///
+ /// Set the property to 0 before calling this method.
+ ///
+ ///
+ /// The caller retains ownership of the stream and is responsible for disposal.
+ /// The stream content is copied during container startup, so the stream must remain open and readable until the container starts.
+ ///
+ ///
+ /// The with the tar archive contents.
+ /// The path where tar archive contents should be placed.
+ /// Cancellation token.
+ /// A task that completes when the tar archive has been copied.
+ Task CopyTarArchiveAsync(Stream tarArchive, string containerPath = "/", CancellationToken ct = default);
+
///
/// Reads a file from the container.
///
diff --git a/tests/Testcontainers.Platform.Linux.Tests/CopyTarArchiveTests.cs b/tests/Testcontainers.Platform.Linux.Tests/CopyTarArchiveTests.cs
new file mode 100644
index 000000000..5cf4da183
--- /dev/null
+++ b/tests/Testcontainers.Platform.Linux.Tests/CopyTarArchiveTests.cs
@@ -0,0 +1,156 @@
+using ICSharpCode.SharpZipLib.Core;
+using System.Formats.Tar;
+
+namespace Testcontainers.Tests
+{
+ public class CopyTarArchiveTests : FileTestBase
+ {
+ public CopyTarArchiveTests() : base()
+ {
+ }
+
+ [Fact]
+ public async Task Should_Copy_TarArchive_ToContainer_BigFile_SystemIO()
+ {
+ const long fiveMb = 5L * 1024L * 1024L;
+ const long fiveGb = fiveMb * 1024L;
+
+ // Given
+ using (var fs = new FileStream(_testFile.FullName, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
+ {
+ fs.SetLength(fiveMb); // change for fiveGb
+ fs.Close();
+ }
+
+ var targetDirectoryPath1 = string.Join("/", string.Empty, "tmp", string.Empty);
+
+ var targetFilePaths = new List();
+ targetFilePaths.Add(Path.Combine(targetDirectoryPath1, _testFile.Name));
+
+ var bufferFilePath = Path.Combine(_testFile.Directory.Parent.FullName, Path.GetRandomFileName());
+
+ using var tarBuffer = new FileStream(bufferFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
+ await TarFile.CreateFromDirectoryAsync(_testFile.Directory.FullName, tarBuffer, false, TestContext.Current.CancellationToken);
+ await tarBuffer.FlushAsync(TestContext.Current.CancellationToken);
+ tarBuffer.Position = 0;
+
+ await using var container = new ContainerBuilder(CommonImages.Alpine)
+ .WithEntrypoint(CommonCommands.SleepInfinity)
+ .WithCopyTarArchive(tarBuffer, targetDirectoryPath1)
+ .Build();
+
+ // When
+ await container.StartAsync(TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ var execResults = await Task.WhenAll(targetFilePaths.Select(containerFilePath => container.ExecAsync(new[] { "test", "-f", containerFilePath }, TestContext.Current.CancellationToken)))
+ .ConfigureAwait(true);
+
+ Assert.All(execResults, result => Assert.Equal(0, result.ExitCode));
+
+ // add proper cleanup in case of test failure?
+ if (File.Exists(bufferFilePath))
+ File.Delete(bufferFilePath);
+ }
+
+ [Fact]
+ public async Task Should_Copy_TarArchive_ToContainer_SystemIO()
+ {
+ // Given
+ var targetDirectoryPath1 = string.Join("/", string.Empty, "tmp", string.Empty);
+
+ var targetFilePaths = new List();
+ targetFilePaths.Add(Path.Combine(targetDirectoryPath1, _testFile.Name));
+
+ using var memStore = new MemoryStream(); // the underlying storage for tar archive, can be any Stream.
+ await TarFile.CreateFromDirectoryAsync(_testFile.Directory.FullName, memStore, false, TestContext.Current.CancellationToken);
+ memStore.Position = 0; // must rewind underlying Stream to start position before copying
+
+ await using var container = new ContainerBuilder(CommonImages.Alpine)
+ .WithEntrypoint(CommonCommands.SleepInfinity)
+ .WithCopyTarArchive(memStore, targetDirectoryPath1) // this will copy the Stream with tar archive contents in container just before the container startup
+ .Build();
+
+ // When
+ await container.StartAsync(TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ var execResults = await Task.WhenAll(targetFilePaths.Select(containerFilePath => container.ExecAsync(new[] { "test", "-f", containerFilePath }, TestContext.Current.CancellationToken)))
+ .ConfigureAwait(true);
+
+ Assert.All(execResults, result => Assert.Equal(0, result.ExitCode));
+ }
+
+ [Fact]
+ public async Task Should_Copy_TarArchive_ToContainer_SharpZipLib()
+ {
+ static string ToTarArchivePath(string s)
+ {
+ return PathUtils.DropPathRoot(s).Replace(Path.DirectorySeparatorChar, '/');
+ }
+
+ // Given
+ var targetFilePath1 = string.Join("/", string.Empty, "tmp", Guid.NewGuid(), _testFile.Name);
+ var targetFilePath2 = string.Join("/", string.Empty, "tmp", Guid.NewGuid(), _testFile.Name);
+
+ var targetFilePaths = new List();
+ targetFilePaths.Add(targetFilePath1);
+ targetFilePaths.Add(targetFilePath2);
+ if (OperatingSystem.IsWindows())
+ {
+ targetFilePaths.Add(ToTarArchivePath(_testFile.FullName));
+ }
+
+ using var memStore = new MemoryStream(); // the underlying storage for tar archive, can be any Stream.
+ using var tarArchive = TarArchive.CreateOutputTarArchive(memStore, Encoding.UTF8);
+ tarArchive.IsStreamOwner = false; // setting this property to false is required for copying underlying Stream into container later.
+
+ // entry #1 from the file on host disk, using full path to file
+ var entry1 = ICSharpCode.SharpZipLib.Tar.TarEntry.CreateEntryFromFile(_testFile.FullName);
+ // explicitly set a path for file in container to targetFilePath1
+ entry1.Name = targetFilePath1;
+ tarArchive.WriteEntry(entry1, false);
+
+ // entry #2
+ var entry2 = ICSharpCode.SharpZipLib.Tar.TarEntry.CreateEntryFromFile(_testFile.FullName);
+ entry2.Name = targetFilePath2;
+ // custom userid:groupid
+ entry2.UserId = 1000;
+ entry2.GroupId = 1000;
+ tarArchive.WriteEntry(entry2, false);
+
+ if (OperatingSystem.IsWindows())
+ {
+ // entry #3
+ var entry3 = ICSharpCode.SharpZipLib.Tar.TarEntry.CreateEntryFromFile(_testFile.FullName);
+ // on Windows entry5.Name will be without C:\\ prefix
+ tarArchive.WriteEntry(entry3, false);
+ }
+
+ // close the TarArchive, forcing it to write all neccessary data as bytes into underlying Stream
+ tarArchive.Close();
+ memStore.Position = 0; // must rewind underlying Stream to start position before copying
+
+ await using var container = new ContainerBuilder(CommonImages.Alpine)
+ .WithEntrypoint(CommonCommands.SleepInfinity)
+ .WithCopyTarArchive(memStore) // this will copy the Stream with tar archive contents in container just before the container startup
+ .Build();
+
+ // When
+ await container.StartAsync(TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ var execResults = await Task.WhenAll(targetFilePaths.Select(containerFilePath => container.ExecAsync(new[] { "test", "-f", containerFilePath }, TestContext.Current.CancellationToken)))
+ .ConfigureAwait(true);
+
+ Assert.All(execResults, result => Assert.Equal(0, result.ExitCode));
+
+ // check that uid is correct
+ var result = await container.ExecAsync(new[] { "stat", "-c", "'%u'", targetFilePath2 }, TestContext.Current.CancellationToken);
+ Assert.Equal("'1000'\n", result.Stdout);
+ }
+ }
+}
diff --git a/tests/Testcontainers.Platform.Linux.Tests/FileTestBase.cs b/tests/Testcontainers.Platform.Linux.Tests/FileTestBase.cs
new file mode 100644
index 000000000..be65b1a25
--- /dev/null
+++ b/tests/Testcontainers.Platform.Linux.Tests/FileTestBase.cs
@@ -0,0 +1,48 @@
+namespace Testcontainers.Tests
+{
+ public abstract class FileTestBase : IDisposable
+ {
+ protected readonly FileInfo _testFile = new FileInfo(Path.Combine(TestSession.TempDirectoryPath, Guid.NewGuid().ToString("D"), Path.GetRandomFileName()));
+
+ private bool _disposed;
+
+ protected FileTestBase()
+ {
+ _ = Directory.CreateDirectory(_testFile.Directory!.FullName);
+
+ using var fileStream = _testFile.Open(FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
+ fileStream.WriteByte(13);
+ }
+
+ ~FileTestBase()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void DisposeManagedResources()
+ {
+ _testFile.Directory!.Delete(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ DisposeManagedResources();
+ }
+
+ _disposed = true;
+ }
+ }
+}
diff --git a/tests/Testcontainers.Platform.Linux.Tests/TarOutputMemoryStreamTest.cs b/tests/Testcontainers.Platform.Linux.Tests/TarOutputMemoryStreamTest.cs
index a375daa02..e03066f86 100644
--- a/tests/Testcontainers.Platform.Linux.Tests/TarOutputMemoryStreamTest.cs
+++ b/tests/Testcontainers.Platform.Linux.Tests/TarOutputMemoryStreamTest.cs
@@ -1,43 +1,20 @@
namespace Testcontainers.Tests;
-public abstract class TarOutputMemoryStreamTest : IDisposable
+public abstract class TarOutputMemoryStreamTest : FileTestBase
{
private const string TargetDirectoryPath = "/tmp";
private readonly TarOutputMemoryStream _tarOutputMemoryStream = new TarOutputMemoryStream(TargetDirectoryPath, NullLogger.Instance);
- private readonly FileInfo _testFile = new FileInfo(Path.Combine(TestSession.TempDirectoryPath, Guid.NewGuid().ToString("D"), Path.GetRandomFileName()));
-
- private bool _disposed;
-
- protected TarOutputMemoryStreamTest()
- {
- _ = Directory.CreateDirectory(_testFile.Directory!.FullName);
-
- using var fileStream = _testFile.Open(FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
- fileStream.WriteByte(13);
- }
-
- public void Dispose()
+ protected TarOutputMemoryStreamTest() : base()
{
- Dispose(true);
- GC.SuppressFinalize(this);
}
- protected virtual void Dispose(bool disposing)
+ protected override void DisposeManagedResources()
{
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- _tarOutputMemoryStream.Dispose();
- _testFile.Directory!.Delete(true);
- }
+ base.DisposeManagedResources();
- _disposed = true;
+ _tarOutputMemoryStream.Dispose();
}
[Fact]