using Unity.SharpZipLib.GZip; using Unity.SharpZipLib.Tests.TestSupport; using NUnit.Framework; using System; using System.IO; using System.Text; namespace Unity.SharpZipLib.Tests.GZip { /// /// This class contains test cases for GZip compression /// [TestFixture] internal class GZipTestSuite { /// /// Basic compress/decompress test /// [Test] [Category("GZip")] public void TestGZip() { var ms = new MemoryStream(); var outStream = new GZipOutputStream(ms); byte[] buf = new byte[100000]; var rnd = new Random(); rnd.NextBytes(buf); outStream.Write(buf, 0, buf.Length); outStream.Flush(); outStream.Finish(); ms.Seek(0, SeekOrigin.Begin); var inStream = new GZipInputStream(ms); byte[] buf2 = new byte[buf.Length]; int currentIndex = 0; int count = buf2.Length; while (true) { int numRead = inStream.Read(buf2, currentIndex, count); if (numRead <= 0) { break; } currentIndex += numRead; count -= numRead; } Assert.AreEqual(0, count); for (int i = 0; i < buf.Length; ++i) { Assert.AreEqual(buf2[i], buf[i]); } } /// /// Writing GZip headers is delayed so that this stream can be used with HTTP/IIS. /// [Test] [Category("GZip")] public void DelayedHeaderWriteNoData() { var ms = new MemoryStream(); Assert.AreEqual(0, ms.Length); using (GZipOutputStream outStream = new GZipOutputStream(ms)) { Assert.AreEqual(0, ms.Length); } byte[] data = ms.ToArray(); Assert.IsTrue(data.Length > 0); } /// /// Variant of DelayedHeaderWriteNoData testing flushing for https://github.com/icsharpcode/SharpZipLib/issues/382 /// [Test] [Category("GZip")] public void DelayedHeaderWriteFlushNoData() { var ms = new MemoryStream(); Assert.AreEqual(0, ms.Length); using (GZipOutputStream outStream = new GZipOutputStream(ms) { IsStreamOwner = false }) { // #382 - test flushing the stream before writing to it. outStream.Flush(); } ms.Seek(0, SeekOrigin.Begin); // Test that the gzip stream can be read var readStream = new MemoryStream(); using (GZipInputStream inStream = new GZipInputStream(ms)) { inStream.CopyTo(readStream); } byte[] data = readStream.ToArray(); Assert.That(data, Is.Empty, "Should not have any decompressed data"); } /// /// Writing GZip headers is delayed so that this stream can be used with HTTP/IIS. /// [Test] [Category("GZip")] public void DelayedHeaderWriteWithData() { var ms = new MemoryStream(); Assert.AreEqual(0, ms.Length); using (GZipOutputStream outStream = new GZipOutputStream(ms)) { Assert.AreEqual(0, ms.Length); outStream.WriteByte(45); // Should in fact contain header right now with // 1 byte in the compression pipeline Assert.AreEqual(10, ms.Length); } byte[] data = ms.ToArray(); Assert.IsTrue(data.Length > 0); } /// /// variant of DelayedHeaderWriteWithData to test https://github.com/icsharpcode/SharpZipLib/issues/382 /// [Test] [Category("GZip")] public void DelayedHeaderWriteFlushWithData() { var ms = new MemoryStream(); Assert.AreEqual(0, ms.Length); using (GZipOutputStream outStream = new GZipOutputStream(ms) { IsStreamOwner = false }) { Assert.AreEqual(0, ms.Length); // #382 - test flushing the stream before writing to it. outStream.Flush(); outStream.WriteByte(45); } ms.Seek(0, SeekOrigin.Begin); // Test that the gzip stream can be read var readStream = new MemoryStream(); using (GZipInputStream inStream = new GZipInputStream(ms)) { inStream.CopyTo(readStream); } // Check that the data was read byte[] data = readStream.ToArray(); CollectionAssert.AreEqual(new byte[] { 45 }, data, "Decompressed data should match initial data"); } [Test] [Category("GZip")] public void ZeroLengthInputStream() { var gzi = new GZipInputStream(new MemoryStream()); bool exception = false; int retval = int.MinValue; try { retval = gzi.ReadByte(); } catch { exception = true; } Assert.IsFalse(exception, "reading from an empty stream should not cause an exception"); Assert.That(retval, Is.EqualTo(-1), "should yield -1 byte value"); } [Test] [Category("GZip")] public void OutputStreamOwnership() { var memStream = new TrackedMemoryStream(); var s = new GZipOutputStream(memStream); Assert.IsFalse(memStream.IsClosed, "Shouldnt be closed initially"); Assert.IsFalse(memStream.IsDisposed, "Shouldnt be disposed initially"); s.Close(); Assert.IsTrue(memStream.IsClosed, "Should be closed after parent owner close"); Assert.IsTrue(memStream.IsDisposed, "Should be disposed after parent owner close"); memStream = new TrackedMemoryStream(); s = new GZipOutputStream(memStream); Assert.IsFalse(memStream.IsClosed, "Shouldnt be closed initially"); Assert.IsFalse(memStream.IsDisposed, "Shouldnt be disposed initially"); s.IsStreamOwner = false; s.Close(); Assert.IsFalse(memStream.IsClosed, "Should not be closed after parent owner close"); Assert.IsFalse(memStream.IsDisposed, "Should not be disposed after parent owner close"); } [Test] [Category("GZip")] public void InputStreamOwnership() { var memStream = new TrackedMemoryStream(); var s = new GZipInputStream(memStream); Assert.IsFalse(memStream.IsClosed, "Shouldnt be closed initially"); Assert.IsFalse(memStream.IsDisposed, "Shouldnt be disposed initially"); s.Close(); Assert.IsTrue(memStream.IsClosed, "Should be closed after parent owner close"); Assert.IsTrue(memStream.IsDisposed, "Should be disposed after parent owner close"); memStream = new TrackedMemoryStream(); s = new GZipInputStream(memStream); Assert.IsFalse(memStream.IsClosed, "Shouldnt be closed initially"); Assert.IsFalse(memStream.IsDisposed, "Shouldnt be disposed initially"); s.IsStreamOwner = false; s.Close(); Assert.IsFalse(memStream.IsClosed, "Should not be closed after parent owner close"); Assert.IsFalse(memStream.IsDisposed, "Should not be disposed after parent owner close"); } [Test] public void DoubleFooter() { var memStream = new TrackedMemoryStream(); var s = new GZipOutputStream(memStream); s.Finish(); Int64 length = memStream.Length; s.Close(); Assert.AreEqual(length, memStream.ToArray().Length); } [Test] public void DoubleClose() { var memStream = new TrackedMemoryStream(); var s = new GZipOutputStream(memStream); s.Finish(); s.Close(); s.Close(); memStream = new TrackedMemoryStream(); using (GZipOutputStream no2 = new GZipOutputStream(memStream)) { s.Close(); } } [Test] public void WriteAfterFinish() { var memStream = new TrackedMemoryStream(); var s = new GZipOutputStream(memStream); s.Finish(); try { s.WriteByte(7); Assert.Fail("Write should fail"); } catch { } } [Test] public void WriteAfterClose() { var memStream = new TrackedMemoryStream(); var s = new GZipOutputStream(memStream); s.Close(); try { s.WriteByte(7); Assert.Fail("Write should fail"); } catch { } } /// /// Verify that if a decompression was successful for at least one block we're exiting gracefully. /// [Test] public void TrailingGarbage() { /* ARRANGE */ var ms = new MemoryStream(); var outStream = new GZipOutputStream(ms); // input buffer to be compressed byte[] buf = new byte[100000]; var rnd = new Random(); rnd.NextBytes(buf); // compress input buffer outStream.Write(buf, 0, buf.Length); outStream.Flush(); outStream.Finish(); // generate random trailing garbage and add to the compressed stream byte[] garbage = new byte[4096]; rnd.NextBytes(garbage); ms.Write(garbage, 0, garbage.Length); // rewind the concatenated stream ms.Seek(0, SeekOrigin.Begin); /* ACT */ // decompress concatenated stream var inStream = new GZipInputStream(ms); byte[] buf2 = new byte[buf.Length]; int currentIndex = 0; int count = buf2.Length; while (true) { int numRead = inStream.Read(buf2, currentIndex, count); if (numRead <= 0) { break; } currentIndex += numRead; count -= numRead; } /* ASSERT */ Assert.AreEqual(0, count); for (int i = 0; i < buf.Length; ++i) { Assert.AreEqual(buf2[i], buf[i]); } } /// /// Test that if we flush a GZip output stream then all data that has been written /// is flushed through to the underlying stream and can be successfully read back /// even if the stream is not yet finished. /// [Test] [Category("GZip")] public void FlushToUnderlyingStream() { var ms = new MemoryStream(); var outStream = new GZipOutputStream(ms); byte[] buf = new byte[100000]; var rnd = new Random(); rnd.NextBytes(buf); outStream.Write(buf, 0, buf.Length); // Flush output stream but don't finish it yet outStream.Flush(); ms.Seek(0, SeekOrigin.Begin); var inStream = new GZipInputStream(ms); byte[] buf2 = new byte[buf.Length]; int currentIndex = 0; int count = buf2.Length; while (true) { try { int numRead = inStream.Read(buf2, currentIndex, count); if (numRead <= 0) { break; } currentIndex += numRead; count -= numRead; } catch (GZipException) { // We should get an unexpected EOF exception once we've read all // data as the stream isn't yet finished. break; } } Assert.AreEqual(0, count); for (int i = 0; i < buf.Length; ++i) { Assert.AreEqual(buf2[i], buf[i]); } } [Test] [Category("GZip")] public void SmallBufferDecompression() { var outputBufferSize = 100000; var inputBufferSize = outputBufferSize * 4; var outputBuffer = new byte[outputBufferSize]; var inputBuffer = new byte[inputBufferSize]; using (var msGzip = new MemoryStream()) { using (var gzos = new GZipOutputStream(msGzip)) { gzos.IsStreamOwner = false; var rnd = new Random(0); rnd.NextBytes(inputBuffer); gzos.Write(inputBuffer, 0, inputBuffer.Length); gzos.Flush(); gzos.Finish(); } msGzip.Seek(0, SeekOrigin.Begin); using (var gzis = new GZipInputStream(msGzip)) using (var msRaw = new MemoryStream()) { int readOut; while ((readOut = gzis.Read(outputBuffer, 0, outputBuffer.Length)) > 0) { msRaw.Write(outputBuffer, 0, readOut); } var resultBuffer = msRaw.ToArray(); for (var i = 0; i < resultBuffer.Length; i++) { Assert.AreEqual(inputBuffer[i], resultBuffer[i]); } } } } /// /// Should gracefully handle reading from a stream that becomes unreadable after /// all of the data has been read. /// /// /// Test for https://github.com/icsharpcode/SharpZipLib/issues/379 /// [Test] [Category("Zip")] public void ShouldGracefullyHandleReadingANonReableStream() { MemoryStream ms = new SelfClosingStream(); using (var gzos = new GZipOutputStream(ms)) { gzos.IsStreamOwner = false; byte[] buf = new byte[100000]; var rnd = new Random(); rnd.NextBytes(buf); gzos.Write(buf, 0, buf.Length); } ms.Seek(0, SeekOrigin.Begin); using (var gzis = new GZipInputStream(ms)) using (var msRaw = new MemoryStream()) { gzis.CopyTo(msRaw); } } [Test] [Category("GZip")] [Category("Performance")] [Category("Long Running")] [Explicit("Long Running")] public void WriteThroughput() { PerformanceTesting.TestWrite( size: TestDataSize.Large, output: w => new GZipOutputStream(w) ); } [Test] [Category("GZip")] [Category("Performance")] [Explicit("Long Running")] public void ReadWriteThroughput() { PerformanceTesting.TestReadWrite( size: TestDataSize.Large, input: w => new GZipInputStream(w), output: w => new GZipOutputStream(w) ); } /// /// Basic compress/decompress test /// [Test] [Category("GZip")] public void OriginalFilename() { var content = "FileContents"; using (var ms = new MemoryStream()) { using (var outStream = new GZipOutputStream(ms) { IsStreamOwner = false }) { outStream.FileName = "/path/to/file.ext"; var writeBuffer = Encoding.ASCII.GetBytes(content); outStream.Write(writeBuffer, 0, writeBuffer.Length); outStream.Flush(); outStream.Finish(); } ms.Seek(0, SeekOrigin.Begin); using (var inStream = new GZipInputStream(ms)) { var readBuffer = new byte[content.Length]; inStream.Read(readBuffer, 0, readBuffer.Length); Assert.AreEqual(content, Encoding.ASCII.GetString(readBuffer)); Assert.AreEqual("file.ext", inStream.GetFilename()); } } } } }