mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-07-10 19:17:20 +00:00
* Initial plan * Fix IsSolidAsync() returning false for 7z solid archives SevenZipArchive was missing an override for IsSolidAsync(), so the base class default (always returning false) was used. Added an override that uses the same logic as the sync IsSolid property: load entries asynchronously and check if any folder has more than one file. Also updated existing async tests to use IsSolidAsync() instead of casting to SevenZipArchive to call the sync IsSolid, and added a new SevenZipArchive_TestSolidDetectionAsync test that mirrors the existing sync SevenZipArchive_TestSolidDetection test. Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/60f8daec-e784-4845-a65c-5a493fbb53ef Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
340 lines
12 KiB
C#
340 lines
12 KiB
C#
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using SharpCompress.Archives;
|
|
using SharpCompress.Archives.SevenZip;
|
|
using SharpCompress.Readers;
|
|
using SharpCompress.Test.Mocks;
|
|
using Xunit;
|
|
|
|
namespace SharpCompress.Test.SevenZip;
|
|
|
|
public class SevenZipArchiveAsyncTests : ArchiveTests
|
|
{
|
|
[Fact]
|
|
public async Task SevenZipArchive_LZMA_AsyncStreamExtraction()
|
|
{
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.LZMA.7z");
|
|
#if NETFRAMEWORK
|
|
using var stream = File.OpenRead(testArchive);
|
|
#else
|
|
await using var stream = File.OpenRead(testArchive);
|
|
#endif
|
|
await using var archive = await ArchiveFactory.OpenAsyncArchive(
|
|
new AsyncOnlyStream(stream)
|
|
);
|
|
|
|
await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
|
|
{
|
|
var targetPath = Path.Combine(SCRATCH_FILES_PATH, entry.Key!);
|
|
var targetDir = Path.GetDirectoryName(targetPath);
|
|
|
|
if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
|
|
{
|
|
Directory.CreateDirectory(targetDir);
|
|
}
|
|
|
|
#if NETFRAMEWORK
|
|
using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#else
|
|
await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
using var targetStream = File.Create(targetPath);
|
|
#else
|
|
await using var targetStream = File.Create(targetPath);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None);
|
|
#else
|
|
await sourceStream.CopyToAsync(targetStream, CancellationToken.None);
|
|
#endif
|
|
}
|
|
|
|
VerifyFiles();
|
|
}
|
|
|
|
//[Fact]
|
|
public async Task SevenZipArchive_LZMA2_AsyncStreamExtraction()
|
|
{
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.LZMA2.7z");
|
|
#if NETFRAMEWORK
|
|
using var stream = File.OpenRead(testArchive);
|
|
#else
|
|
await using var stream = File.OpenRead(testArchive);
|
|
#endif
|
|
await using var archive = await ArchiveFactory.OpenAsyncArchive(
|
|
new AsyncOnlyStream(stream)
|
|
);
|
|
|
|
await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
|
|
{
|
|
var targetPath = Path.Combine(SCRATCH_FILES_PATH, entry.Key!);
|
|
var targetDir = Path.GetDirectoryName(targetPath);
|
|
|
|
if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
|
|
{
|
|
Directory.CreateDirectory(targetDir);
|
|
}
|
|
|
|
#if NETFRAMEWORK
|
|
using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#else
|
|
await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
using var targetStream = File.Create(targetPath);
|
|
#else
|
|
await using var targetStream = File.Create(targetPath);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None);
|
|
#else
|
|
await sourceStream.CopyToAsync(targetStream, CancellationToken.None);
|
|
#endif
|
|
}
|
|
|
|
VerifyFiles();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SevenZipArchive_Solid_AsyncStreamExtraction()
|
|
{
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z");
|
|
#if NETFRAMEWORK
|
|
using var stream = File.OpenRead(testArchive);
|
|
#else
|
|
await using var stream = File.OpenRead(testArchive);
|
|
#endif
|
|
await using var archive = await ArchiveFactory.OpenAsyncArchive(
|
|
new AsyncOnlyStream(stream)
|
|
);
|
|
|
|
await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
|
|
{
|
|
var targetPath = Path.Combine(SCRATCH_FILES_PATH, entry.Key!);
|
|
var targetDir = Path.GetDirectoryName(targetPath);
|
|
|
|
if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
|
|
{
|
|
Directory.CreateDirectory(targetDir);
|
|
}
|
|
|
|
#if NETFRAMEWORK
|
|
using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#else
|
|
await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
using var targetStream = File.Create(targetPath);
|
|
#else
|
|
await using var targetStream = File.Create(targetPath);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None);
|
|
#else
|
|
await sourceStream.CopyToAsync(targetStream, CancellationToken.None);
|
|
#endif
|
|
}
|
|
|
|
VerifyFiles();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SevenZipArchive_BZip2_AsyncStreamExtraction()
|
|
{
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.BZip2.7z");
|
|
#if NETFRAMEWORK
|
|
using var stream = File.OpenRead(testArchive);
|
|
#else
|
|
await using var stream = File.OpenRead(testArchive);
|
|
#endif
|
|
await using var archive = await ArchiveFactory.OpenAsyncArchive(
|
|
new AsyncOnlyStream(stream)
|
|
);
|
|
|
|
await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
|
|
{
|
|
var targetPath = Path.Combine(SCRATCH_FILES_PATH, entry.Key!);
|
|
var targetDir = Path.GetDirectoryName(targetPath);
|
|
|
|
if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
|
|
{
|
|
Directory.CreateDirectory(targetDir);
|
|
}
|
|
|
|
#if NETFRAMEWORK
|
|
using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#else
|
|
await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
using var targetStream = File.Create(targetPath);
|
|
#else
|
|
await using var targetStream = File.Create(targetPath);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None);
|
|
#else
|
|
await sourceStream.CopyToAsync(targetStream, CancellationToken.None);
|
|
#endif
|
|
}
|
|
|
|
VerifyFiles();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SevenZipArchive_PPMd_AsyncStreamExtraction()
|
|
{
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.PPMd.7z");
|
|
#if NETFRAMEWORK
|
|
using var stream = File.OpenRead(testArchive);
|
|
#else
|
|
await using var stream = File.OpenRead(testArchive);
|
|
#endif
|
|
await using var archive = await ArchiveFactory.OpenAsyncArchive(
|
|
new AsyncOnlyStream(stream)
|
|
);
|
|
|
|
await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
|
|
{
|
|
var targetPath = Path.Combine(SCRATCH_FILES_PATH, entry.Key!);
|
|
var targetDir = Path.GetDirectoryName(targetPath);
|
|
|
|
if (!string.IsNullOrEmpty(targetDir) && !Directory.Exists(targetDir))
|
|
{
|
|
Directory.CreateDirectory(targetDir);
|
|
}
|
|
|
|
#if NETFRAMEWORK
|
|
using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#else
|
|
await using var sourceStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
using var targetStream = File.Create(targetPath);
|
|
#else
|
|
await using var targetStream = File.Create(targetPath);
|
|
#endif
|
|
#if NETFRAMEWORK
|
|
await sourceStream.CopyToAsync(targetStream, 81920, CancellationToken.None);
|
|
#else
|
|
await sourceStream.CopyToAsync(targetStream, CancellationToken.None);
|
|
#endif
|
|
}
|
|
|
|
VerifyFiles();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SevenZipArchive_TestSolidDetectionAsync()
|
|
{
|
|
await using var oneBlockSolidArchive = await SevenZipArchive.OpenAsyncArchive(
|
|
Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.1block.7z")
|
|
);
|
|
Assert.True(await oneBlockSolidArchive.IsSolidAsync());
|
|
|
|
await using var solidArchive = await SevenZipArchive.OpenAsyncArchive(
|
|
Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z")
|
|
);
|
|
Assert.True(await solidArchive.IsSolidAsync());
|
|
|
|
await using var nonSolidArchive = await SevenZipArchive.OpenAsyncArchive(
|
|
Path.Combine(TEST_ARCHIVES_PATH, "7Zip.nonsolid.7z")
|
|
);
|
|
Assert.False(await nonSolidArchive.IsSolidAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SevenZipArchive_Solid_ExtractAllEntries_Contiguous_Async()
|
|
{
|
|
// This test verifies that solid archives iterate entries as contiguous streams
|
|
// rather than recreating the decompression stream for each entry
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z");
|
|
await using var archive = await SevenZipArchive.OpenAsyncArchive(testArchive);
|
|
Assert.True(await archive.IsSolidAsync());
|
|
|
|
await using var reader = await archive.ExtractAllEntriesAsync();
|
|
while (await reader.MoveToNextEntryAsync())
|
|
{
|
|
if (!reader.Entry.IsDirectory)
|
|
{
|
|
await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH);
|
|
}
|
|
}
|
|
|
|
VerifyFiles();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SevenZipArchive_Solid_VerifyStreamReuse()
|
|
{
|
|
// This test verifies that the folder stream is reused within each folder
|
|
// and not recreated for each entry in solid archives
|
|
var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z");
|
|
await using var archive = await SevenZipArchive.OpenAsyncArchive(testArchive);
|
|
Assert.True(await archive.IsSolidAsync());
|
|
|
|
await using var reader = await archive.ExtractAllEntriesAsync();
|
|
|
|
var sevenZipReader = Assert.IsType<SevenZipArchive.SevenZipReader>(reader);
|
|
sevenZipReader.DiagnosticsEnabled = true;
|
|
|
|
Stream? currentFolderStreamInstance = null;
|
|
object? currentFolder = null;
|
|
var entryCount = 0;
|
|
var entriesInCurrentFolder = 0;
|
|
var streamRecreationsWithinFolder = 0;
|
|
|
|
while (await reader.MoveToNextEntryAsync())
|
|
{
|
|
if (!reader.Entry.IsDirectory)
|
|
{
|
|
// Extract the entry to trigger GetEntryStream
|
|
using var entryStream = await reader.OpenEntryStreamAsync();
|
|
var buffer = new byte[4096];
|
|
while (entryStream.Read(buffer, 0, buffer.Length) > 0)
|
|
{
|
|
// Read the stream to completion
|
|
}
|
|
|
|
entryCount++;
|
|
|
|
var folderStream = sevenZipReader.DiagnosticsCurrentFolderStream;
|
|
var folder = sevenZipReader.DiagnosticsCurrentFolder;
|
|
|
|
Assert.NotNull(folderStream); // Folder stream should exist
|
|
|
|
// Check if we're in a new folder
|
|
if (currentFolder == null || !ReferenceEquals(currentFolder, folder))
|
|
{
|
|
// Starting a new folder
|
|
currentFolder = folder;
|
|
currentFolderStreamInstance = folderStream;
|
|
entriesInCurrentFolder = 1;
|
|
}
|
|
else
|
|
{
|
|
// Same folder - verify stream wasn't recreated
|
|
entriesInCurrentFolder++;
|
|
|
|
if (!ReferenceEquals(currentFolderStreamInstance, folderStream))
|
|
{
|
|
// Stream was recreated within the same folder - this is the bug we're testing for!
|
|
streamRecreationsWithinFolder++;
|
|
}
|
|
|
|
currentFolderStreamInstance = folderStream;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify we actually tested multiple entries
|
|
Assert.True(entryCount > 1, "Test should have multiple entries to verify stream reuse");
|
|
|
|
// The critical check: within a single folder, the stream should NEVER be recreated
|
|
Assert.Equal(0, streamRecreationsWithinFolder); // Folder stream should remain the same for all entries in the same folder
|
|
}
|
|
}
|