diff --git a/tests/SharpCompress.Test/GZip/GZipWriterNonSeekableTests.cs b/tests/SharpCompress.Test/GZip/GZipWriterNonSeekableTests.cs
new file mode 100644
index 00000000..3ee01e86
--- /dev/null
+++ b/tests/SharpCompress.Test/GZip/GZipWriterNonSeekableTests.cs
@@ -0,0 +1,73 @@
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Archives.GZip;
+using SharpCompress.Test.Mocks;
+using SharpCompress.Writers.GZip;
+using Xunit;
+
+namespace SharpCompress.Test.GZip;
+
+///
+/// Regression tests for writing gzip streams to non-seekable (forward-only) output streams.
+/// GZip is inherently a single forward stream with no seek-dependent layout, so streaming to
+/// a must simply produce a valid, readable gzip stream.
+///
+public class GZipWriterNonSeekableTests
+{
+ private const string EntryName = "content.txt";
+
+ private static byte[] CreateContent() =>
+ Encoding.UTF8.GetBytes(string.Concat(Enumerable.Repeat("Hello streaming gzip! ", 500)));
+
+ private static async Task WriteToNonSeekableAsync(byte[] content)
+ {
+ using var ms = new MemoryStream();
+ await using (var writer = new GZipWriter(new ForwardOnlyStream(ms)))
+ {
+ using var source = new MemoryStream(content);
+ await writer.WriteAsync(EntryName, source, null);
+ }
+ return ms.ToArray();
+ }
+
+ private static byte[] WriteToNonSeekableSync(byte[] content)
+ {
+ using var ms = new MemoryStream();
+ using (var writer = new GZipWriter(new ForwardOnlyStream(ms)))
+ {
+ using var source = new MemoryStream(content);
+ writer.Write(EntryName, source, null);
+ }
+ return ms.ToArray();
+ }
+
+ private static void AssertRoundTrips(byte[] gz, byte[] expected)
+ {
+ using var archive = GZipArchive.OpenArchive(new MemoryStream(gz));
+ var entry = archive.Entries.First();
+ using var extracted = new MemoryStream();
+ using (var entryStream = entry.OpenEntryStream())
+ {
+ entryStream.CopyTo(extracted);
+ }
+ Assert.Equal(expected, extracted.ToArray());
+ }
+
+ [Fact]
+ public async Task GZip_Async_NonSeekable_RoundTrips()
+ {
+ var content = CreateContent();
+ var gz = await WriteToNonSeekableAsync(content);
+ AssertRoundTrips(gz, content);
+ }
+
+ [Fact]
+ public void GZip_Sync_NonSeekable_RoundTrips()
+ {
+ var content = CreateContent();
+ var gz = WriteToNonSeekableSync(content);
+ AssertRoundTrips(gz, content);
+ }
+}
diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipWriterNonSeekableTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipWriterNonSeekableTests.cs
new file mode 100644
index 00000000..16c0d7a2
--- /dev/null
+++ b/tests/SharpCompress.Test/SevenZip/SevenZipWriterNonSeekableTests.cs
@@ -0,0 +1,25 @@
+using System.IO;
+using SharpCompress.Common;
+using SharpCompress.Test.Mocks;
+using SharpCompress.Writers.SevenZip;
+using Xunit;
+
+namespace SharpCompress.Test.SevenZip;
+
+///
+/// 7z writing requires a seekable output stream so the signature header can be back-patched on
+/// finalize (see SevenZipWriter.cs). Unlike Zip, it cannot fall back to a streaming layout, so
+/// constructing a writer over a non-seekable output must fail fast. This pins that documented
+/// limitation against silent regression.
+///
+public class SevenZipWriterNonSeekableTests
+{
+ [Fact]
+ public void SevenZip_NonSeekable_Output_Throws()
+ {
+ using var ms = new MemoryStream();
+ Assert.Throws(() =>
+ new SevenZipWriter(new ForwardOnlyStream(ms), new SevenZipWriterOptions())
+ );
+ }
+}
diff --git a/tests/SharpCompress.Test/Tar/TarWriterNonSeekableTests.cs b/tests/SharpCompress.Test/Tar/TarWriterNonSeekableTests.cs
new file mode 100644
index 00000000..454c20a6
--- /dev/null
+++ b/tests/SharpCompress.Test/Tar/TarWriterNonSeekableTests.cs
@@ -0,0 +1,129 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpCompress.Archives.Tar;
+using SharpCompress.Common;
+using SharpCompress.Test.Mocks;
+using SharpCompress.Writers.Tar;
+using Xunit;
+
+namespace SharpCompress.Test.Tar;
+
+///
+/// Regression tests for writing tar archives to non-seekable (forward-only) output streams.
+/// Tar is a forward-only format with no header back-patching, so streaming to a
+/// must produce a valid, readable archive. The sources are
+/// seekable so the writer can derive each entry's size up front (see TarWriter.cs).
+///
+public class TarWriterNonSeekableTests
+{
+ private static readonly DateTime FixedModificationTime = new(2024, 5, 15, 10, 30, 0);
+
+ private static (string Name, byte[] Content)[] CreateStreamingTestEntries() =>
+ [
+ (
+ "first.txt",
+ Encoding.UTF8.GetBytes(
+ string.Concat(Enumerable.Repeat("Hello streaming tar! ", 500))
+ )
+ ),
+ (
+ "nested/second.txt",
+ Encoding.UTF8.GetBytes(
+ string.Concat(Enumerable.Repeat("Another entry with different content. ", 300))
+ )
+ ),
+ ];
+
+ private static async Task WriteArchiveToNonSeekableAsync(
+ (string Name, byte[] Content)[] entries
+ )
+ {
+ using var ms = new MemoryStream();
+ await using (
+ var writer = new TarWriter(
+ new ForwardOnlyStream(ms),
+ new TarWriterOptions(CompressionType.None, true)
+ )
+ )
+ {
+ foreach (var (name, content) in entries)
+ {
+ using var source = new MemoryStream(content);
+ await writer.WriteAsync(name, source, FixedModificationTime);
+ }
+ }
+ return ms.ToArray();
+ }
+
+ private static byte[] WriteArchiveToNonSeekableSync((string Name, byte[] Content)[] entries)
+ {
+ using var ms = new MemoryStream();
+ using (
+ var writer = new TarWriter(
+ new ForwardOnlyStream(ms),
+ new TarWriterOptions(CompressionType.None, true)
+ )
+ )
+ {
+ foreach (var (name, content) in entries)
+ {
+ using var source = new MemoryStream(content);
+ writer.Write(name, source, FixedModificationTime);
+ }
+ }
+ return ms.ToArray();
+ }
+
+ private static async Task AssertRoundTripsAsync(
+ byte[] tar,
+ (string Name, byte[] Content)[] entries
+ )
+ {
+ using var archive = TarArchive.OpenArchive(new MemoryStream(tar));
+ var fileEntries = archive.Entries.Where(e => !e.IsDirectory).ToList();
+ Assert.Equal(entries.Length, fileEntries.Count);
+ foreach (var (name, content) in entries)
+ {
+ var entry = fileEntries.Single(e => e.Key == name);
+ using var extracted = new MemoryStream();
+#if LEGACY_DOTNET
+ using (var entryStream = await entry.OpenEntryStreamAsync())
+#else
+ await using (var entryStream = await entry.OpenEntryStreamAsync())
+#endif
+ {
+ await entryStream.CopyToAsync(extracted);
+ }
+ Assert.Equal(content, extracted.ToArray());
+ }
+ }
+
+ [Fact]
+ public async Task Tar_Async_NonSeekable_RoundTrips()
+ {
+ var entries = CreateStreamingTestEntries();
+ var tar = await WriteArchiveToNonSeekableAsync(entries);
+ await AssertRoundTripsAsync(tar, entries);
+ }
+
+ [Fact]
+ public async Task Tar_Sync_NonSeekable_RoundTrips()
+ {
+ var entries = CreateStreamingTestEntries();
+ var tar = WriteArchiveToNonSeekableSync(entries);
+ await AssertRoundTripsAsync(tar, entries);
+ }
+
+ [Fact]
+ public async Task Tar_Async_NonSeekable_Matches_Sync_Output()
+ {
+ var entries = CreateStreamingTestEntries();
+ var asyncTar = await WriteArchiveToNonSeekableAsync(entries);
+ var syncTar = WriteArchiveToNonSeekableSync(entries);
+
+ Assert.Equal(syncTar, asyncTar);
+ }
+}