mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-24 16:05:27 +00:00
Dynamic RingBuffer for BZip2: expand buffer after format detection
Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/2d1412f8-34f8-4a32-8802-e52770342940 Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
ddcbb3e474
commit
ca52cec0b3
@@ -127,4 +127,96 @@ public class SharpCompressStreamSeekTest
|
||||
Assert.Equal(3, readBuffer[0]);
|
||||
Assert.Equal(4, readBuffer[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureMinimumRewindBufferSize_ExpandsSmallBuffer_PreservesExistingData()
|
||||
{
|
||||
// Arrange: create a stream with a small initial buffer (size 10)
|
||||
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 });
|
||||
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
|
||||
var stream = SharpCompressStream.Create(nonSeekableMs, 10);
|
||||
stream.StartRecording();
|
||||
|
||||
// Read 4 bytes — they are now in the ring buffer
|
||||
var buffer = new byte[8];
|
||||
stream.Read(buffer, 0, 4);
|
||||
Assert.Equal(4, stream.Position);
|
||||
|
||||
// Rewind to verify 4 bytes are present
|
||||
stream.Rewind();
|
||||
|
||||
// Act: expand the ring buffer to 200 bytes while data is present
|
||||
stream.EnsureMinimumRewindBufferSize(200);
|
||||
|
||||
// Verify the data is still replayable after expansion
|
||||
var readBuffer = new byte[4];
|
||||
stream.Read(readBuffer, 0, 4);
|
||||
Assert.Equal(1, readBuffer[0]);
|
||||
Assert.Equal(2, readBuffer[1]);
|
||||
Assert.Equal(3, readBuffer[2]);
|
||||
Assert.Equal(4, readBuffer[3]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureMinimumRewindBufferSize_BufferAlreadyLarger_DoesNotShrink()
|
||||
{
|
||||
// Arrange: create a stream with a large initial buffer (size 200)
|
||||
var ms = new MemoryStream(new byte[] { 1, 2, 3, 4, 5 });
|
||||
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
|
||||
var stream = SharpCompressStream.Create(nonSeekableMs, 200);
|
||||
stream.StartRecording();
|
||||
stream.Read(new byte[5], 0, 5);
|
||||
|
||||
// Act: request a smaller minimum — buffer should stay at 200
|
||||
stream.EnsureMinimumRewindBufferSize(50);
|
||||
|
||||
// Assert: buffer can still hold the 5 bytes written before expansion request
|
||||
stream.Rewind();
|
||||
var readBuffer = new byte[5];
|
||||
stream.Read(readBuffer, 0, 5);
|
||||
Assert.Equal(1, readBuffer[0]);
|
||||
Assert.Equal(5, readBuffer[4]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureMinimumRewindBufferSize_AllowsRewindAfterLargeRead()
|
||||
{
|
||||
// Simulate the BZip2 scenario: small initial buffer, expand after format detection,
|
||||
// then verify a large read still allows Rewind.
|
||||
const int initialSize = 10;
|
||||
const int expandedSize = 100;
|
||||
const int largeReadSize = 80;
|
||||
|
||||
var data = new byte[100];
|
||||
for (var i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = (byte)(i + 1);
|
||||
}
|
||||
|
||||
var ms = new MemoryStream(data);
|
||||
var nonSeekableMs = new NonSeekableStreamWrapper(ms);
|
||||
var stream = SharpCompressStream.Create(nonSeekableMs, initialSize);
|
||||
stream.StartRecording();
|
||||
|
||||
// Read 4 bytes (format detection — magic bytes)
|
||||
var buffer = new byte[4];
|
||||
stream.Read(buffer, 0, 4);
|
||||
stream.Rewind();
|
||||
|
||||
// Expand the ring buffer to cover the anticipated large probe read
|
||||
stream.EnsureMinimumRewindBufferSize(expandedSize);
|
||||
|
||||
// Read a large amount (simulating BZip2 block decompression)
|
||||
var largeBuffer = new byte[largeReadSize];
|
||||
stream.Read(largeBuffer, 0, largeReadSize);
|
||||
|
||||
// Rewind must succeed even though largeReadSize > initialSize
|
||||
stream.Rewind();
|
||||
|
||||
// Verify data replays correctly
|
||||
var verifyBuffer = new byte[largeReadSize];
|
||||
stream.Read(verifyBuffer, 0, largeReadSize);
|
||||
Assert.Equal(data[0], verifyBuffer[0]);
|
||||
Assert.Equal(data[largeReadSize - 1], verifyBuffer[largeReadSize - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.BZip2;
|
||||
using SharpCompress.Factories;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Tar;
|
||||
using SharpCompress.Test.Mocks;
|
||||
@@ -58,6 +60,53 @@ public class TarReaderTests : ReaderTests
|
||||
[Fact]
|
||||
public void Tar_GZip_OldGnu_Reader() => Read("Tar.oldgnu.tar.gz", CompressionType.GZip);
|
||||
|
||||
[Fact]
|
||||
public void Tar_BZip2_Reader_NonSeekable()
|
||||
{
|
||||
// Regression test for: Dynamic default RingBuffer for BZip2
|
||||
// Opening a .tar.bz2 from a non-seekable stream should succeed
|
||||
// because EnsureMinimumRewindBufferSize expands the ring buffer
|
||||
// to hold the BZip2 block before calling IsTarFile.
|
||||
using var fs = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar.bz2"));
|
||||
using var nonSeekable = new ForwardOnlyStream(fs);
|
||||
using var reader = ReaderFactory.OpenReader(nonSeekable);
|
||||
var entryCount = 0;
|
||||
while (reader.MoveToNextEntry())
|
||||
{
|
||||
if (!reader.Entry.IsDirectory)
|
||||
{
|
||||
entryCount++;
|
||||
}
|
||||
}
|
||||
Assert.True(entryCount > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarWrapper_BZip2_MinimumRewindBufferSize_IsMaxBZip2BlockSize()
|
||||
{
|
||||
// The BZip2 TarWrapper must declare a MinimumRewindBufferSize large enough
|
||||
// to hold an entire maximum-size compressed BZip2 block (9 × 100 000 bytes).
|
||||
var bzip2Wrapper = Array.Find(
|
||||
TarWrapper.Wrappers,
|
||||
w => w.CompressionType == CompressionType.BZip2
|
||||
);
|
||||
Assert.NotNull(bzip2Wrapper);
|
||||
Assert.Equal(BZip2Constants.baseBlockSize * 9, bzip2Wrapper.MinimumRewindBufferSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarWrapper_Default_MinimumRewindBufferSize_Is_DefaultRewindableBufferSize()
|
||||
{
|
||||
// Non-BZip2 wrappers that don't specify a custom size default to
|
||||
// Constants.RewindableBufferSize so existing behaviour is unchanged.
|
||||
var noneWrapper = Array.Find(
|
||||
TarWrapper.Wrappers,
|
||||
w => w.CompressionType == CompressionType.None
|
||||
);
|
||||
Assert.NotNull(noneWrapper);
|
||||
Assert.Equal(Common.Constants.RewindableBufferSize, noneWrapper.MinimumRewindBufferSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tar_BZip2_Entry_Stream()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user