Add Compression binary compat tests
authorIan Hays <ianha@microsoft.com>
Wed, 1 Jun 2016 18:02:58 +0000 (11:02 -0700)
committerIan Hays <ianha@microsoft.com>
Fri, 1 Jul 2016 15:07:59 +0000 (08:07 -0700)
We don't currently test binary equality of outputted test zips, so tested compatibility is limited to the parts of the header we expose via a ZipArchive API. This commit adds some tests that compare normal and unicode zips between the current version and .NET 4.5 and .NET 4.6.

Commit migrated from https://github.com/dotnet/corefx/commit/f7840734bdcf1686d185a6df3405d0c5128ff280

src/libraries/Common/tests/System/IO/Compression/StreamHelpers.cs
src/libraries/Common/tests/System/IO/Compression/ZipTestHelper.cs
src/libraries/System.IO.Compression/tests/System.IO.Compression.Tests.csproj
src/libraries/System.IO.Compression/tests/ZipArchive/zip_ManualAndCompatibilityTests.cs

index 1c7fd57..20d4007 100644 (file)
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
 
 public static partial class StreamHelpers
 {
-    public static async Task<Stream> CreateTempCopyStream(String path)
+    public static async Task<MemoryStream> CreateTempCopyStream(String path)
     {
         var bytes = File.ReadAllBytes(path);
 
index 3fa59a1..2809b77 100644 (file)
@@ -180,7 +180,7 @@ namespace System.IO.Compression.Tests
                             DateTime lower = file.LastModifiedDate.AddSeconds(-zipTimestampResolution);
                             DateTime upper = file.LastModifiedDate.AddSeconds(zipTimestampResolution);
                             Assert.InRange(entry.LastWriteTime.Ticks, lower.Ticks, upper.Ticks);
-}
+                        }
 
                         Assert.Equal(file.Name, entry.Name);
                         Assert.Equal(entryName, entry.FullName);
@@ -292,7 +292,8 @@ namespace System.IO.Compression.Tests
                     {
                         String entryName = i.FullName;
 
-                        archive.CreateEntry(entryName.Replace('\\', '/') + "/");
+                        ZipArchiveEntry e = archive.CreateEntry(entryName.Replace('\\', '/') + "/");
+                        e.LastWriteTime = i.LastModifiedDate;
                     }
                 }
 
@@ -307,10 +308,7 @@ namespace System.IO.Compression.Tests
                         if (installStream != null)
                         {
                             ZipArchiveEntry e = archive.CreateEntry(entryName.Replace('\\', '/'));
-                            try
-                            { e.LastWriteTime = i.LastModifiedDate; }
-                            catch (ArgumentOutOfRangeException)
-                            { e.LastWriteTime = DateTimeOffset.Now; }
+                            e.LastWriteTime = i.LastModifiedDate;
                             using (Stream entryStream = e.Open())
                             {
                                 installStream.CopyTo(entryStream);
index 79c0768..c6185f6 100644 (file)
@@ -65,7 +65,7 @@
     </Compile>
   </ItemGroup>
   <ItemGroup>
-    <SupplementalTestData Include="$(PackagesDir)System.IO.Compression.TestData\1.0.2-prerelease\content\**\*.*">
+    <SupplementalTestData Include="$(PackagesDir)System.IO.Compression.TestData\1.0.3-prerelease\content\**\*.*">
       <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
     </SupplementalTestData>
   </ItemGroup>
index 3ed95b4..af4e7a3 100644 (file)
@@ -17,7 +17,8 @@ namespace System.IO.Compression.Tests
         [InlineData("xceedstreaming.zip", "normal", true, true)]
         public static async Task CompatibilityTests(string zipFile, string zipFolder, bool dontRequireExplicit, bool dontCheckTimes)
         {
-            IsZipSameAsDir(await StreamHelpers.CreateTempCopyStream(compat(zipFile)), zfolder(zipFolder), ZipArchiveMode.Update, dontRequireExplicit, dontCheckTimes);}
+            IsZipSameAsDir(await StreamHelpers.CreateTempCopyStream(compat(zipFile)), zfolder(zipFolder), ZipArchiveMode.Update, dontRequireExplicit, dontCheckTimes);
+        }
 
         [Theory]
         [InlineData("excel.xlsx", "excel", true, true)]
@@ -54,5 +55,157 @@ namespace System.IO.Compression.Tests
                 Assert.Equal(fileName, entry.Name);
             }
         }
+
+        /// <summary>
+        /// This test compares binary content of a zip produced by the current version with a zip produced by
+        /// other frameworks. It does this by searching the two zips for the header signature and then
+        /// it compares the subsequent header values for equality.
+        /// 
+        /// This test looks for the local file headers that each entry within a zip possesses and compares these
+        /// values:
+        /// local file header signature     4 bytes  (0x04034b50)
+        /// version needed to extract       2 bytes
+        /// general purpose bit flag        2 bytes
+        /// compression method              2 bytes
+        /// last mod file time              2 bytes
+        /// last mod file date              2 bytes
+        /// 
+        /// it does not compare these values:
+        /// 
+        /// crc-32                          4 bytes
+        /// compressed size                 4 bytes
+        /// uncompressed size               4 bytes
+        /// file name length                2 bytes
+        /// extra field length              2 bytes
+        /// file name(variable size)
+        /// extra field(variable size)
+        /// </summary>
+        [Theory]
+        [InlineData("net45_unicode.zip", "unicode")]
+        [InlineData("net46_unicode.zip", "unicode")]
+        [InlineData("net45_normal.zip", "normal")]
+        [InlineData("net46_normal.zip", "normal")]
+        public static async Task ZipBinaryCompat_LocalFileHeaders(string zipFile, string zipFolder)
+        {
+            using (MemoryStream actualArchiveStream = new MemoryStream())
+            using (MemoryStream expectedArchiveStream = await StreamHelpers.CreateTempCopyStream(compat(zipFile)))
+            {
+                byte[] localFileHeaderSignature = new byte[] { 0x50, 0x4b, 0x03, 0x04 };
+                
+                // Produce a ZipFile
+                await CreateFromDir(zfolder(zipFolder), actualArchiveStream, ZipArchiveMode.Create);
+
+                // Read the streams to byte arrays
+                byte[] actualBytes = actualArchiveStream.ToArray();
+                byte[] expectedBytes = expectedArchiveStream.ToArray();
+
+                // Search for the file headers
+                int actualIndex = 0, expectedIndex = 0;
+                while ((expectedIndex = FindIndexOfSequence(expectedBytes, expectedIndex, localFileHeaderSignature)) != -1)
+                {
+                    actualIndex = FindIndexOfSequence(actualBytes, actualIndex, localFileHeaderSignature);
+                    Assert.NotEqual(-1, actualIndex);
+                    for (int i = 0; i < 14; i++)
+                    {
+                        Assert.Equal(expectedBytes[expectedIndex], actualBytes[actualIndex]);
+                    }
+                    expectedIndex += 14;
+                    actualIndex += 14;
+                }
+            }
+        }
+
+        /// <summary>
+        /// This test compares binary content of a zip produced by the current version with a zip produced by
+        /// other frameworks. It does this by searching the two zips for the header signature and then
+        /// it compares the subsequent header values for equality.
+        /// 
+        /// This test looks for the central directory headers that each entry within a zip possesses and compares these
+        /// values:
+        /// central file header signature   4 bytes  (0x02014b50)
+        /// version made by                 2 bytes
+        /// version needed to extract       2 bytes
+        /// general purpose bit flag        2 bytes
+        /// compression method              2 bytes
+        /// last mod file time              2 bytes
+        /// last mod file date              2 bytes
+        /// uncompressed size               4 bytes
+        /// file name length                2 bytes
+        /// extra field length              2 bytes
+        /// file comment length             2 bytes
+        /// disk number start               2 bytes
+        /// internal file attributes        2 bytes
+        /// external file attributes        4 bytes
+        ///
+        /// it does not compare these values:
+        /// crc-32                          4 bytes
+        /// compressed size                 4 bytes
+        /// relative offset of local header 4 bytes
+        /// file name (variable size)
+        /// extra field (variable size)
+        /// file comment (variable size)
+        /// </summary>
+        [Theory]
+        [InlineData("net45_unicode.zip", "unicode")]
+        [InlineData("net46_unicode.zip", "unicode")]
+        [InlineData("net45_normal.zip", "normal")]
+        [InlineData("net46_normal.zip", "normal")]
+        public static async Task ZipBinaryCompat_CentralDirectoryHeaders(string zipFile, string zipFolder)
+        {
+            using (MemoryStream actualArchiveStream = new MemoryStream())
+            using (MemoryStream expectedArchiveStream = await StreamHelpers.CreateTempCopyStream(compat(zipFile)))
+            {
+                byte[] signature = new byte[] { 0x50, 0x4b, 0x03, 0x04 };
+
+                // Produce a ZipFile
+                await CreateFromDir(zfolder(zipFolder), actualArchiveStream, ZipArchiveMode.Create);
+
+                // Read the streams to byte arrays
+                byte[] actualBytes = actualArchiveStream.ToArray();
+                byte[] expectedBytes = expectedArchiveStream.ToArray();
+
+                // Search for the file headers
+                int actualIndex = 0, expectedIndex = 0;
+                while ((expectedIndex = FindIndexOfSequence(expectedBytes, expectedIndex, signature)) != -1)
+                {
+                    actualIndex = FindIndexOfSequence(actualBytes, actualIndex, signature);
+                    Assert.NotEqual(-1, actualIndex);
+                    for (int i = 0; i < 16; i++)
+                    {
+                        Assert.Equal(expectedBytes[expectedIndex], actualBytes[actualIndex]);
+                    }
+                    for (int i = 24; i < 42; i++)
+                    {
+                        Assert.Equal(expectedBytes[expectedIndex], actualBytes[actualIndex]);
+                    }
+                    expectedIndex += 38;
+                    actualIndex += 38;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Simple helper method to search <paramref name="bytesToSearch"/> for the exact byte sequence specified by
+        /// <paramref name="sequenceToFind"/>, starting at <paramref name="startIndex"/>.
+        /// </summary>
+        /// <returns>The first index of the first element in the matching sequence</returns>
+        public static int FindIndexOfSequence(byte[] bytesToSearch, int startIndex, byte[] sequenceToFind)
+        {
+            for (int index = startIndex; index < bytesToSearch.Length - sequenceToFind.Length; index++)
+            {
+                bool equal = true;
+                for (int i = 0; i < sequenceToFind.Length; i++)
+                {
+                    if (bytesToSearch[index + i] != sequenceToFind[i])
+                    {
+                        equal = false;
+                        break;
+                    }
+                }
+                if (equal)
+                    return index;
+            }
+            return -1;
+        }
     }
 }