Skip to content

Commit d339358

Browse files
authored
Add a new more complex unit test (#105)
Add a new more complete unit test which emulates how Xamarin.Android uses LibZipSharp. Also made a small change to the way we handle reading from a stream. According to the documentation stream.Read can return a value less than what was asked. So we need to keep reading until we get all the data we expected or an error is thrown.
1 parent 9bccd74 commit d339358

File tree

3 files changed

+152
-2
lines changed

3 files changed

+152
-2
lines changed

LibZipSharp.UnitTest/ZipTests.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,85 @@ public void CheckForUnknownCompressionMethods ()
257257
}
258258
}
259259

260+
[Test]
261+
public void SimulateXamarinAndroidUsage ([Values (true, false)] bool copyArchive, [Values (true, false)] bool useFiles)
262+
{
263+
string filePath = Path.GetFullPath ("packaged_resources");
264+
if (!File.Exists (filePath)) {
265+
filePath = Path.GetFullPath (Path.Combine ("LibZipSharp.UnitTest", "packaged_resources"));
266+
}
267+
268+
string baseArchive = $"base_{copyArchive}_{useFiles}.zip";
269+
270+
if (File.Exists (baseArchive))
271+
File.Delete (baseArchive);
272+
273+
var mode = FileMode.Create;
274+
if (copyArchive) {
275+
File.Copy (filePath, baseArchive);
276+
mode = FileMode.Open;
277+
}
278+
279+
List<(string name, CompressionMethod c, ulong size)> expectedFiles = new List<(string name, CompressionMethod c, ulong size)> ();
280+
// use a specific seed so we always generate the same files
281+
Random rnd = new Random (3456464);
282+
283+
using (var baseZip = new ZipWrapper (baseArchive, mode)) {
284+
using (var zip = ZipArchive.Open (filePath, FileMode.Open)) {
285+
foreach (var entry in zip) {
286+
var entryName = entry.FullName;
287+
if (entryName.Contains ("\\")) {
288+
entryName = entryName.Replace ('\\', '/');
289+
}
290+
expectedFiles.Add ((entryName, entry.CompressionMethod, entry.Size));
291+
if (copyArchive)
292+
continue;
293+
var ms = new MemoryStream ();
294+
entry.Extract (ms);
295+
TestContext.Out.WriteLine ($"Adding {entryName} to base.zip");
296+
baseZip.Archive.AddStream (ms, entryName, compressionMethod: entry.CompressionMethod);
297+
}
298+
}
299+
300+
baseZip.FixupWindowsPathSeparators ((a, b) => TestContext.Out.WriteLine ($"Fixing up malformed entry `{a}` -> `{b}`"));
301+
302+
for (int i=0; i< 200; i++) {
303+
uint fileSize = (uint)rnd.Next (341, 3535592);
304+
byte[] buffer = new byte[fileSize];
305+
rnd.NextBytes (buffer);
306+
CompressionMethod compression = rnd.NextDouble () < 0.8 ? CompressionMethod.Deflate : CompressionMethod.Store;
307+
string entryName = $"temp/file_{i}_size_{fileSize}_{compression}.bin";
308+
string archivePath = rnd.NextDouble () < 0.3 ? entryName : Path.GetFileName (entryName);
309+
TestContext.Out.WriteLine ($"Adding {entryName} to base.zip");
310+
if (useFiles) {
311+
Directory.CreateDirectory ("temp");
312+
File.WriteAllBytes (entryName, buffer);
313+
baseZip.Archive.AddFile (entryName, archivePath, compressionMethod: compression);
314+
} else {
315+
var ms = new MemoryStream (buffer);
316+
ms.Position = 0;
317+
baseZip.Archive.AddStream (ms, archivePath, compressionMethod: compression);
318+
}
319+
expectedFiles.Add ((archivePath, compression, fileSize));
320+
321+
if (rnd.NextDouble () < 0.2)
322+
baseZip.Flush ();
323+
}
324+
325+
}
326+
using (var zip = ZipArchive.Open (baseArchive, FileMode.Open, strictConsistencyChecks: true)) {
327+
foreach (var file in expectedFiles) {
328+
Assert.IsTrue (zip.ContainsEntry (file.name), $"zip should contain {file}.");
329+
var e = zip.ReadEntry(file.name);
330+
Assert.AreEqual (file.size, e.Size, $"{file} should be {file.size} but was {e.Size}.");
331+
Assert.AreEqual (file.c, e.CompressionMethod, $"{file} should be {file.c} but was {e.CompressionMethod}.");
332+
}
333+
}
334+
335+
if (File.Exists (baseArchive))
336+
File.Delete (baseArchive);
337+
}
338+
260339
[Test]
261340
public void InMemoryZipFile ()
262341
{

LibZipSharp.UnitTest/ZipWrapper.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using NUnit.Framework;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Text;
6+
using System.Threading;
7+
using Xamarin.Tools.Zip;
8+
using Unix = Mono.Unix;
9+
10+
namespace Tests {
11+
public class ZipWrapper : IDisposable {
12+
ZipArchive archive;
13+
string filename;
14+
public ZipArchive Archive => archive;
15+
16+
public ZipWrapper (string file, FileMode mode = FileMode.CreateNew) {
17+
filename = file;
18+
archive = ZipArchive.Open (filename, mode);
19+
}
20+
21+
public void Flush () {
22+
if (archive != null) {
23+
archive.Close ();
24+
archive.Dispose ();
25+
archive = null;
26+
}
27+
archive = ZipArchive.Open (filename, FileMode.Open);
28+
}
29+
30+
/// <summary>
31+
/// HACK: aapt2 is creating zip entries on Windows such as `assets\subfolder/asset2.txt`
32+
/// </summary>
33+
public void FixupWindowsPathSeparators (Action<string, string> onRename)
34+
{
35+
bool modified = false;
36+
foreach (var entry in archive) {
37+
if (entry.FullName.Contains ("\\")) {
38+
var name = entry.FullName.Replace ('\\', '/');
39+
onRename?.Invoke (entry.FullName, name);
40+
entry.Rename (name);
41+
modified = true;
42+
}
43+
}
44+
if (modified) {
45+
Flush ();
46+
}
47+
}
48+
49+
public void Dispose ()
50+
{
51+
Dispose(true);
52+
GC.SuppressFinalize (this);
53+
}
54+
55+
protected virtual void Dispose(bool disposing) {
56+
if (disposing) {
57+
if (archive != null) {
58+
archive.Close ();
59+
archive.Dispose ();
60+
archive = null;
61+
}
62+
}
63+
}
64+
}
65+
}

LibZipSharp/Xamarin.Tools.Zip/ZipArchive.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -852,8 +852,14 @@ internal static unsafe Int64 stream_callback (IntPtr state, IntPtr data, UInt64
852852
length = (int)Math.Min (stream.Length - stream.Position, length);
853853
buffer = ArrayPool<byte>.Shared.Rent (length);
854854
try {
855-
int bytesRead = stream.Read (buffer, 0, length);
856-
Marshal.Copy (buffer, 0, data, bytesRead);
855+
int bytesRead = 0;
856+
int startIndex = 0;
857+
while (length > 0) {
858+
bytesRead = stream.Read (buffer, 0, length);
859+
Marshal.Copy (buffer, startIndex, data, bytesRead);
860+
startIndex += bytesRead;
861+
length -= bytesRead;
862+
}
857863
return bytesRead;
858864
} finally {
859865
ArrayPool<byte>.Shared.Return (buffer);

0 commit comments

Comments
 (0)