aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-07-07 16:13:40 +0000
committerGravatar Han-Wen Nienhuys <hanwen@google.com>2015-07-07 16:33:30 +0000
commit5a9ec720d1224f1360fa2bf732a869a22c17afc1 (patch)
tree0a5263ae5814f49c6d4099d3176d6b346f3ac5fb /src/test
parent2fd9960f0bc43eff04b8bc317e635c754a67dd27 (diff)
Open source tests for android/ziputils.
-- MOS_MIGRATED_REVID=97677526
Diffstat (limited to 'src/test')
-rw-r--r--src/test/java/BUILD17
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/AllTests.java24
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/BufferedFileTest.java229
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/CentralDirectoryTest.java124
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/DataDescriptorTest.java130
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/DirectoryEntryTest.java188
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/EndOfCentralDirectoryTest.java133
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/FakeFileSystem.java312
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/FileSystem.java78
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/LocalFileHeaderTest.java139
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/ScanUtilTest.java116
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/SplitZipTest.java436
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/SplitterTest.java345
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/ViewTest.java191
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/ZipFileBuilder.java164
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/ZipInTest.java558
-rw-r--r--src/test/java/com/google/devtools/build/android/ziputils/ZipOutTest.java51
17 files changed, 3235 insertions, 0 deletions
diff --git a/src/test/java/BUILD b/src/test/java/BUILD
index 0e9e9221fc..8605d282e0 100644
--- a/src/test/java/BUILD
+++ b/src/test/java/BUILD
@@ -511,6 +511,22 @@ java_test(
)
java_test(
+ name = "ziputils-tests",
+ srcs = glob(["com/google/devtools/build/android/ziputils/*.java"]),
+ args = ["com.google.devtools.build.android.ziputils.AllTests"],
+ tags = ["ziputils"],
+ deps = [
+ ":testutil",
+ "//src/tools/android/java/com/google/devtools/build/android/ziputils:splitter_lib",
+ "//src/tools/android/java/com/google/devtools/build/android/ziputils:ziputils_lib",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ "//third_party:junit4",
+ "//third_party:truth",
+ ],
+)
+
+java_test(
name = "common-rules-tests",
srcs = glob(["com/google/devtools/build/lib/rules/filegroup/*.java"]),
args = ["com.google.devtools.build.lib.AllTests"],
@@ -531,6 +547,7 @@ java_test(
)
TEST_SUITES = [
+ "ziputils",
"rules",
"analysis",
"foundations",
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/AllTests.java b/src/test/java/com/google/devtools/build/android/ziputils/AllTests.java
new file mode 100644
index 0000000000..a69b95dc10
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/AllTests.java
@@ -0,0 +1,24 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import com.google.devtools.build.lib.testutil.ClasspathSuite;
+
+import org.junit.runner.RunWith;
+
+/**
+ * Test suite for options parsing framework.
+ */
+@RunWith(ClasspathSuite.class)
+public class AllTests {}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/BufferedFileTest.java b/src/test/java/com/google/devtools/build/android/ziputils/BufferedFileTest.java
new file mode 100644
index 0000000000..6a51756dd3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/BufferedFileTest.java
@@ -0,0 +1,229 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Unit tests for {@link BufferedFile}.
+ */
+@RunWith(JUnit4.class)
+public class BufferedFileTest {
+
+ private static final FakeFileSystem fileSystem = new FakeFileSystem();
+
+ @Test
+ public void testBufferedFile() throws Exception {
+ int fileSize = 64;
+ String filename = "bytes64";
+ byte[] bytes = fileData(fileSize);
+ fileSystem.addFile(filename, bytes);
+ FileChannel file = fileSystem.getInputChannel(filename);
+ int maxAlloc = 16;
+ int regionOff = 0;
+ assertException("channel null",
+ null, regionOff, fileSize, maxAlloc, NullPointerException.class);
+ assertException("region offset negative",
+ file, -1, fileSize, maxAlloc, IllegalArgumentException.class);
+ assertException("region size negative",
+ file, regionOff, -1, maxAlloc, IllegalArgumentException.class);
+ assertException("maxAlloc negative",
+ file, regionOff, fileSize, -1, IllegalArgumentException.class);
+ assertException("region exceeds file",
+ file, regionOff, fileSize + 1, maxAlloc, IllegalArgumentException.class);
+ assertException("region too long from offset",
+ file, regionOff + 1, fileSize, maxAlloc, IllegalArgumentException.class);
+ assertException("region short, still too long",
+ file, regionOff + 50, fileSize - 49, maxAlloc, IllegalArgumentException.class);
+
+ new BufferedFile(file, regionOff, fileSize, 0); // alloc minimal buffers for request
+ new BufferedFile(file, regionOff, fileSize, fileSize); // alloc for full region
+ }
+
+ @Test
+ public void testGetBufferThrows() throws Exception {
+ BufferedFile instance;
+
+ int fileSize = 64;
+ String filename = "bytes64";
+ byte[] bytes = fileData(fileSize);
+ fileSystem.addFile(filename, bytes);
+ FileChannel file = fileSystem.getInputChannel(filename);
+ int regionOff = 4;
+ int regionSize = 50;
+ int maxAlloc = 16;
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ assertException("buffer negative size",
+ instance, regionOff, -1, IllegalArgumentException.class);
+ assertException("buffer lower bound",
+ instance, regionOff - 1, regionSize, IllegalArgumentException.class);
+ assertException("buffer upper bound",
+ instance, regionOff + regionSize + 1, 1, IllegalArgumentException.class);
+ assertException("buffer upper bound zero read",
+ instance, regionOff + regionSize + 1, 0, IllegalArgumentException.class);
+ assertException("buffer beyond region non zero read",
+ instance, regionOff + regionSize, 1, IllegalArgumentException.class);
+ }
+
+ @Test
+ public void testGetBufferAllocationLimits() throws Exception {
+ BufferedFile instance;
+ int fileSize = 64;
+ String filename = "bytes64";
+ byte[] bytes = fileData(fileSize);
+ fileSystem.addFile(filename, bytes);
+ FileChannel file = fileSystem.getInputChannel(filename);
+ int regionOff = 4;
+ int regionSize = 50;
+ int maxAlloc = 16;
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ assertCase("buffer, empty, start", instance, regionOff, 0, 0, maxAlloc);
+ assertWithMessage("buffer, empty, start").that(regionOff + regionSize)
+ .isEqualTo(instance.limit());
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ assertCase("buffer, empty, end", instance, regionOff + regionSize, 0, 0, 0);
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ assertCase("buffer, one, end", instance, regionOff + regionSize - 1, 1, 1, 1);
+ assertWithMessage("buffer, one, end").that(regionOff + regionSize)
+ .isEqualTo(instance.limit());
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ assertCase("buffer, small, end", instance, regionOff + regionSize - 2, 2, 2, 2);
+ assertWithMessage("buffer, small, end").that(regionOff + regionSize)
+ .isEqualTo(instance.limit());
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ assertCase("buffer, small, start", instance, regionOff, 2, 2, maxAlloc);
+ assertWithMessage("buffer, small, start").that(regionOff + regionSize)
+ .isEqualTo(instance.limit());
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ assertCase("buffer, all region", instance, regionOff, regionSize, regionSize, regionSize);
+ assertWithMessage("buffer, all region").that(regionOff + regionSize)
+ .isEqualTo(instance.limit());
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ assertCase("buffer, request more",
+ instance, regionOff + 5, regionSize, regionSize - 5, regionSize - 5);
+ assertWithMessage("buffer, request more").that(regionOff + regionSize)
+ .isEqualTo(instance.limit());
+ }
+
+ @Test
+ public void testGetBufferInCache() throws Exception {
+ BufferedFile instance;
+ int fileSize = 64;
+ String filename = "bytes64";
+ byte[] bytes = fileData(fileSize);
+ fileSystem.addFile(filename, bytes);
+ FileChannel file = fileSystem.getInputChannel(filename);
+ int regionOff = 5;
+ int regionSize = 50;
+ int maxAlloc = 20;
+ int cacheOff = regionOff + 5;
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ instance.getBuffer(cacheOff, maxAlloc);
+ assertCase("Cached zero buf", instance, cacheOff, 0, 0, maxAlloc);
+ assertCase("Cached at front", instance, cacheOff, 5, 5, maxAlloc);
+ assertCase("Cached at end", instance, cacheOff + 2, 5, 5, maxAlloc - 2);
+ assertCase("Cached", instance, cacheOff, maxAlloc, maxAlloc, maxAlloc);
+ }
+
+ @Test
+ public void testGetBufferReadMore() throws Exception {
+ BufferedFile instance;
+ int fileSize = 64;
+ String filename = "bytes64";
+ byte[] bytes = fileData(fileSize);
+ fileSystem.addFile(filename, bytes);
+ FileChannel file = fileSystem.getInputChannel(filename);
+ int regionOff = 5;
+ int regionSize = 50;
+ int maxAlloc = 20;
+ int cacheOff = regionOff + 5;
+ int initialRead = maxAlloc / 2;
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ instance.getBuffer(cacheOff, initialRead);
+ assertCase("Read more overlap",
+ instance, cacheOff + 1, initialRead, initialRead, maxAlloc - 1);
+ assertCase("Read more jump",
+ instance, cacheOff + initialRead + 5, 5, 5, maxAlloc - initialRead - 5);
+ }
+
+ @Test
+ public void testGetBufferReallocate() throws Exception {
+ BufferedFile instance;
+ int fileSize = 64;
+ String filename = "bytes64";
+ byte[] bytes = fileData(fileSize);
+ fileSystem.addFile(filename, bytes);
+ FileChannel file = fileSystem.getInputChannel(filename);
+ int regionOff = 5;
+ int regionSize = 50;
+ int maxAlloc = 20;
+ int cacheOff = regionOff + 5;
+ instance = new BufferedFile(file, regionOff, regionSize, maxAlloc);
+ instance.getBuffer(cacheOff, maxAlloc);
+ assertCase("Realloc after", instance, cacheOff + maxAlloc, maxAlloc, maxAlloc, maxAlloc);
+ assertCase("Realloc before", instance, cacheOff, maxAlloc, maxAlloc, maxAlloc);
+ assertCase("Realloc just after", instance, cacheOff + 5, maxAlloc, maxAlloc, maxAlloc);
+ assertCase("Realloc just before", instance, cacheOff, maxAlloc, maxAlloc, maxAlloc);
+ assertCase("Realloc supersize", instance, cacheOff, maxAlloc + 5, maxAlloc + 5, maxAlloc + 5);
+ }
+
+ void assertException(String msg, FileChannel file, long off, long len, int maxAlloc,
+ Class<?> expect) {
+ try {
+ new BufferedFile(file, off, len, maxAlloc);
+ fail(msg + " - no exception");
+ } catch (Exception ex) {
+ assertWithMessage(msg + " - exception, ").that(expect).isSameAs(ex.getClass());
+ }
+ }
+
+ void assertException(String msg, BufferedFile instance, long off, int len, Class<?> expect) {
+ try {
+ instance.getBuffer(off, len);
+ fail(msg + " - no exception");
+ } catch (Exception ex) {
+ assertWithMessage(msg + " - exception, ").that(expect).isSameAs(ex.getClass());
+ }
+ }
+
+ void assertCase(String msg, BufferedFile instance, long off, int len, int expectLimit,
+ int capacityBound) throws IOException {
+ ByteBuffer buf = instance.getBuffer(off, len);
+ assertWithMessage(msg + " - position, ").that(0).isEqualTo(buf.position());
+ assertWithMessage(msg + " - limit, ").that(expectLimit).isEqualTo(buf.limit());
+ assertWithMessage(msg + " - capacity, ").that(buf.capacity()).isAtLeast(expectLimit);
+ assertWithMessage(msg + " - capacity, ").that(buf.capacity()).isAtMost(capacityBound);
+ if (len > 0 && expectLimit > 0) {
+ assertEquals(msg + " - value, ", (byte) off, buf.get(0));
+ }
+ }
+
+ byte[] fileData(int count) {
+ byte[] bytes = new byte[count];
+ for (int i = 0; i < count; i++) {
+ bytes[i] = (byte) i;
+ }
+ return bytes;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/CentralDirectoryTest.java b/src/test/java/com/google/devtools/build/android/ziputils/CentralDirectoryTest.java
new file mode 100644
index 0000000000..e582b527c3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/CentralDirectoryTest.java
@@ -0,0 +1,124 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENTIM;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * Unit tests for {@link CentralDirectory}.
+ */
+@RunWith(JUnit4.class)
+public class CentralDirectoryTest {
+
+ /**
+ * Test of viewOf method, of class CentralDirectory.
+ */
+ @Test
+ public void testViewOf() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 100; i++) {
+ buffer.put((byte) i);
+ }
+ buffer.position(20);
+ buffer.limit(90);
+ CentralDirectory view = CentralDirectory.viewOf(buffer);
+ int expPos = 0;
+ int expLimit = 90;
+ // expect the buffer to have been reset to 0 (CentralDirectory does NOT slice).
+ assertEquals("View not at position 0", expPos, view.buffer.position());
+ assertEquals("Buffer not at position 0", expPos, buffer.position());
+ assertEquals("Buffer limit changed", expLimit, view.buffer.limit());
+ assertEquals("Buffer limit changed", expLimit, buffer.limit());
+ }
+
+ /**
+ * Test of parse method, of class CentralDirectory.
+ */
+ @Test
+ public void testParse() {
+ // First fill it with some entries
+ ByteBuffer inputBuffer = ByteBuffer.allocate(10000).order(ByteOrder.LITTLE_ENDIAN);
+ String comment = null;
+ byte[] extra = null;
+ String filename = "pkg/0.txt";
+ DirectoryEntry entry = DirectoryEntry.view(inputBuffer, filename, extra , comment);
+ int expSize = entry.getSize();
+ comment = "";
+ extra = new byte[]{};
+ for (int i = 1; i < 20; i++) {
+ filename = "pkg/" + i + ".txt";
+ entry = DirectoryEntry.view(inputBuffer, filename, extra , comment);
+ expSize += entry.getSize();
+ extra = new byte[extra.length + 1];
+ comment = comment + "," + i;
+ }
+ // Parse the entries.
+ CentralDirectory cdir = CentralDirectory.viewOf(inputBuffer).at(0).parse();
+ assertEquals("Count", 20, cdir.getCount());
+ assertEquals("Position after parse", expSize, cdir.buffer.position());
+ assertEquals("Limit after parse", 10000, cdir.buffer.limit());
+ cdir.buffer.flip();
+ assertEquals("Position after finish", 0, cdir.buffer.position());
+ assertEquals("Limit after finish", expSize, cdir.buffer.limit());
+ }
+
+ /**
+ * Test of nextEntry method, of class CentralDirectory.
+ */
+ @Test
+ public void testNextEntry() {
+ ByteBuffer outputBuffer = ByteBuffer.allocate(10000).order(ByteOrder.LITTLE_ENDIAN);
+ CentralDirectory cdir = CentralDirectory.viewOf(outputBuffer);
+ String comment = null;
+ byte[] extra = null;
+ String filename = "pkg/0.txt";
+ DirectoryEntry entry = DirectoryEntry.allocate(filename, extra , comment);
+ cdir.nextEntry(entry).set(CENTIM, 0);
+ int expSize = entry.getSize();
+ comment = "";
+ extra = new byte[]{};
+ for (int i = 1; i < 20; i++) {
+ filename = "pkg/" + i + ".txt";
+ entry = DirectoryEntry.allocate(filename, extra , comment);
+ cdir.nextEntry(entry).set(CENTIM, 0);
+ int size = entry.getSize();
+ expSize += size;
+ extra = new byte[extra.length + 1];
+ comment = comment + "," + i;
+ }
+ assertEquals("Count", 20, cdir.getCount());
+ assertEquals("Position after build", expSize, cdir.buffer.position());
+ assertEquals("Limit after build", 10000, cdir.buffer.limit());
+ cdir.buffer.flip();
+ assertEquals("Position after finish build", 0, cdir.buffer.position());
+ assertEquals("Limit after finish build", expSize, cdir.buffer.limit());
+
+ // now try to parse the directory we just created.
+ cdir.at(0).parse();
+ assertEquals("Count", 20, cdir.getCount());
+ assertEquals("Position after re-parse", expSize, cdir.buffer.position());
+ assertEquals("Limit after re-parse", expSize, cdir.buffer.limit());
+ cdir.buffer.flip();
+ assertEquals("Position after finish parse", 0, cdir.buffer.position());
+ assertEquals("Limit after finish parse", expSize, cdir.buffer.limit());
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/DataDescriptorTest.java b/src/test/java/com/google/devtools/build/android/ziputils/DataDescriptorTest.java
new file mode 100644
index 0000000000..24b0c85311
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/DataDescriptorTest.java
@@ -0,0 +1,130 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.devtools.build.android.ziputils.DataDescriptor.EXTCRC;
+import static com.google.devtools.build.android.ziputils.DataDescriptor.EXTLEN;
+import static com.google.devtools.build.android.ziputils.DataDescriptor.EXTSIG;
+import static com.google.devtools.build.android.ziputils.DataDescriptor.EXTSIZ;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Unit tests for {@link DataDescriptor}.
+ */
+@RunWith(JUnit4.class)
+public class DataDescriptorTest {
+
+ /**
+ * Test of viewOf method, of class DataDescriptor.
+ */
+ @Test
+ public void testViewOf() {
+ int[] markers = { 12345678, DataDescriptor.SIGNATURE, 0};
+ for (int marker : markers) {
+ ByteBuffer buffer = ByteBuffer.allocate(50).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 50; i++) {
+ buffer.put((byte) i);
+ }
+ int offset = 20;
+ buffer.putInt(offset, marker);
+ buffer.position(offset);
+ DataDescriptor view = DataDescriptor.viewOf(buffer);
+ int expMark = marker == DataDescriptor.SIGNATURE ? (int) ZipInputStream.EXTSIG : -1;
+ int expSize = marker == DataDescriptor.SIGNATURE ? ZipInputStream.EXTHDR
+ : ZipInputStream.EXTHDR - 4;
+ int expPos = 0;
+ assertEquals("not based at current position[" + marker + "]", expMark, view.get(EXTSIG));
+ assertEquals("Not slice with position 0[" + marker + "]", expPos, view.buffer.position());
+ assertEquals("Not sized with comment[" + marker + "]", expSize, view.getSize());
+ assertEquals("Not limited to size[" + marker + "]", expSize, view.buffer.limit());
+ }
+ }
+
+ /**
+ * Test of view method, of class DataDescriptor.
+ */
+ @Test
+ public void testView_0args() {
+ DataDescriptor view = DataDescriptor.allocate();
+ int expSize = ZipInputStream.EXTHDR;
+ int expPos = 0;
+ int expMarker = (int) ZipInputStream.EXTSIG;
+ assertTrue("no marker", view.hasMarker());
+ assertEquals("No marker", expMarker, view.get(EXTSIG));
+ assertEquals("Not at position 0", expPos, view.buffer.position());
+ assertEquals("Not sized correctly", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ }
+
+ /**
+ * Test of view method, of class DataDescriptor.
+ */
+ @Test
+ public void testView_ByteBuffer() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 100; i++) {
+ buffer.put((byte) i);
+ }
+ buffer.position(50);
+ DataDescriptor view = DataDescriptor.view(buffer);
+ int expMark = (int) ZipInputStream.EXTSIG;
+ int expSize = ZipInputStream.EXTHDR;
+ int expPos = 0;
+ assertEquals("not based at current position", expMark, view.get(EXTSIG));
+ assertEquals("Not slice with position 0", expPos, view.buffer.position());
+ assertEquals("Not sized with comment", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ }
+
+ /**
+ * Test of copy method, of class DataDescriptor.
+ */
+ @Test
+ public void testCopy() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ DataDescriptor view = DataDescriptor.allocate();
+ view.copy(buffer);
+ int expSize = view.getSize();
+ assertEquals("buffer not advanced as expected", expSize, buffer.position());
+ buffer.position(0);
+ DataDescriptor clone = DataDescriptor.viewOf(buffer);
+ assertEquals("Fail to copy mark", view.get(EXTSIG), clone.get(EXTSIG));
+ }
+
+ /**
+ * Test of with and get methods.
+ */
+ @Test
+ public void testWithAndGetMethods() {
+ int crc = 0x12345678;
+ int compressed = 0x357f1d5;
+ int uncompressed = 0x74813159;
+ DataDescriptor view = DataDescriptor.allocate()
+ .set(EXTCRC, crc)
+ .set(EXTSIZ, compressed)
+ .set(EXTLEN, uncompressed);
+ assertEquals("CRC", crc, view.get(EXTCRC));
+ assertEquals("Compressed size", compressed, view.get(EXTSIZ));
+ assertEquals("Uncompressed size", uncompressed, view.get(EXTLEN));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/DirectoryEntryTest.java b/src/test/java/com/google/devtools/build/android/ziputils/DirectoryEntryTest.java
new file mode 100644
index 0000000000..12bbe22b5a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/DirectoryEntryTest.java
@@ -0,0 +1,188 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENATT;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENATX;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENCRC;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENDSK;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENFLG;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENHOW;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENLEN;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENOFF;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENSIG;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENSIZ;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENTIM;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENVEM;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENVER;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Unit tests for {@link DirectoryEntry}.
+ */
+@RunWith(JUnit4.class)
+public class DirectoryEntryTest {
+
+ /**
+ * Test of viewOf method, of class DirectoryEntry.
+ */
+ @Test
+ public void testViewOf() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 100; i++) {
+ buffer.put((byte) i);
+ }
+ int offset = 20;
+ int filenameLength = 10;
+ int extraLength = 6;
+ int commentLength = 8;
+ int marker = DirectoryEntry.SIGNATURE;
+ buffer.putShort(offset + ZipInputStream.CENNAM, (short) filenameLength); // filename length
+ buffer.putShort(offset + ZipInputStream.CENEXT, (short) extraLength); // extra data length
+ buffer.putShort(offset + ZipInputStream.CENCOM, (short) commentLength); // comment length
+ buffer.putInt(20, marker); // any marker
+ buffer.position(offset);
+ DirectoryEntry view = DirectoryEntry.viewOf(buffer);
+ int expMark = (int) ZipInputStream.CENSIG;
+ int expSize = ZipInputStream.CENHDR + filenameLength + extraLength + commentLength;
+ int expPos = 0;
+ assertEquals("not based at current position", expMark, view.get(CENSIG));
+ assertEquals("Not slice with position 0", expPos, view.buffer.position());
+ assertEquals("Not sized with comment", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ }
+
+ /**
+ * Test of view method, of class DirectoryEntry.
+ */
+ @Test
+ public void testView_3Args() {
+ String filename = "pkg/foo.class";
+ String comment = "got milk";
+ byte[] extraData = { 1, 2, 3, 4, 5, 6, 7, 8};
+ int expSize = ZipInputStream.CENHDR + filename.getBytes(UTF_8).length
+ + extraData.length + comment.getBytes(UTF_8).length;
+ int expPos = 0;
+ DirectoryEntry view = DirectoryEntry.allocate(filename, extraData, comment);
+ assertEquals("Incorrect filename", filename, view.getFilename());
+ Assert.assertArrayEquals("Incorrect extra data", extraData, view.getExtraData());
+ assertEquals("Incorrect comment", comment, view.getComment());
+ assertEquals("Not at position 0", expPos, view.buffer.position());
+ assertEquals("Not sized correctly", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ }
+
+ /**
+ * Test of view method, of class DirectoryEntry.
+ */
+ @Test
+ public void testView_4Args() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 100; i++) {
+ buffer.put((byte) i);
+ }
+ int offset = 20;
+ buffer.position(offset);
+ String filename = "pkg/foo.class";
+ byte[] extraData = { 1, 2, 3, 4, 5};
+ String comment = "c";
+ int expMark = (int) ZipInputStream.CENSIG;
+ int expSize = 46 + filename.getBytes(UTF_8).length + extraData.length
+ + comment.getBytes(UTF_8).length;
+ int expPos = 0;
+ DirectoryEntry view = DirectoryEntry.view(buffer, filename, extraData, comment);
+ assertEquals("not based at current position", expMark, view.get(CENSIG));
+ assertEquals("Not slice with position 0", expPos, view.buffer.position());
+ assertEquals("Not sized with filename", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ assertEquals("Incorrect filename", filename, view.getFilename());
+ Assert.assertArrayEquals("Incorrect extra data", extraData, view.getExtraData());
+ assertEquals("Incorrect comment", comment, view.getComment());
+ }
+
+ /**
+ * Test of copy method, of class DirectoryEntry.
+ */
+ @Test
+ public void testCopy() {
+ String filename = "pkg/foo.class";
+ byte[] extraData = {};
+ String comment = "always comment!";
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ DirectoryEntry view = DirectoryEntry.allocate(filename, extraData, comment);
+ view.copy(buffer);
+ int expSize = view.getSize();
+ assertEquals("buffer not advanced as expected", expSize, buffer.position());
+ buffer.position(0);
+ DirectoryEntry clone = DirectoryEntry.viewOf(buffer);
+ assertEquals("Fail to copy mark", view.get(CENSIG), clone.get(CENSIG));
+ assertEquals("Fail to copy comment", view.getFilename(), clone.getFilename());
+ Assert.assertArrayEquals("Fail to copy comment", view.getExtraData(), clone.getExtraData());
+ assertEquals("Fail to copy comment", view.getComment(), clone.getComment());
+ }
+
+ /**
+ * Test of with and get methods.
+ */
+ @Test
+ public void testWithAndGetMethods() {
+ int crc = 0x12345678;
+ int compressed = 0x357f1d5;
+ int uncompressed = 0x74813159;
+ short flags = 0x7a61;
+ short method = 0x3b29;
+ int time = 0x12312345;
+ short version = 0x1234;
+ short versionMadeBy = 0x27a1;
+ short disk = 0x5a78;
+ int extAttr = 0x73b27a15;
+ short intAttr = 0x37cc;
+ int offset = 0x74c93ac1;
+ DirectoryEntry view = DirectoryEntry.allocate("pkg/foo.class", null, "")
+ .set(CENCRC, crc)
+ .set(CENSIZ, compressed)
+ .set(CENLEN, uncompressed)
+ .set(CENFLG, flags)
+ .set(CENHOW, method)
+ .set(CENTIM, time)
+ .set(CENVER, version)
+ .set(CENVEM, versionMadeBy)
+ .set(CENDSK, disk)
+ .set(CENATX, extAttr)
+ .set(CENATT, intAttr)
+ .set(CENOFF, offset);
+ assertEquals("CRC", crc, view.get(CENCRC));
+ assertEquals("Compressed size", compressed, view.get(CENSIZ));
+ assertEquals("Uncompressed size", uncompressed, view.get(CENLEN));
+ assertEquals("Flags", flags, view.get(CENFLG));
+ assertEquals("Method", method, view.get(CENHOW));
+ assertEquals("Modified time", time, view.get(CENTIM));
+ assertEquals("Version needed", version, view.get(CENVER));
+ assertEquals("Version made by", versionMadeBy, view.get(CENVEM));
+ assertEquals("Disk", disk, view.get(CENDSK));
+ assertEquals("External attributes", extAttr, view.get(CENATX));
+ assertEquals("Internal attributes", intAttr, view.get(CENATT));
+ assertEquals("Offset", offset, view.get(CENOFF));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/EndOfCentralDirectoryTest.java b/src/test/java/com/google/devtools/build/android/ziputils/EndOfCentralDirectoryTest.java
new file mode 100644
index 0000000000..55a315d7b0
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/EndOfCentralDirectoryTest.java
@@ -0,0 +1,133 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDDCD;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDDSK;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDOFF;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSIG;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSIZ;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSUB;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDTOT;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Unit tests for {@link EndOfCentralDirectory}.
+ */
+@RunWith(JUnit4.class)
+public class EndOfCentralDirectoryTest {
+ @Test
+ public void testViewOf() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 100; i++) {
+ buffer.put((byte) i);
+ }
+ int offset = 50;
+ int marker = EndOfCentralDirectory.SIGNATURE;
+ int comLength = 8;
+ buffer.putInt(offset, marker);
+ buffer.putShort(offset + ZipInputStream.ENDCOM, (short) comLength);
+ buffer.position(offset);
+ EndOfCentralDirectory view = EndOfCentralDirectory.viewOf(buffer);
+ int expMark = (int) ZipInputStream.ENDSIG;
+ int expSize = ZipInputStream.ENDHDR + comLength; // fixed + comment
+ int expPos = 0;
+ assertEquals("not based at current position", expMark, view.get(ENDSIG));
+ assertEquals("Not slice with position 0", expPos, view.buffer.position());
+ assertEquals("Not sized with comment", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ }
+
+ @Test
+ public void testView_String() {
+ String[] comments = { "hello world", "", null};
+
+ for (String comment : comments) {
+ String expComment = comment != null ? comment : "";
+ EndOfCentralDirectory view = EndOfCentralDirectory.allocate(comment);
+ String commentResult = view.getComment();
+ assertEquals("Incorrect comment", expComment, commentResult);
+ int expSize = ZipInputStream.ENDHDR + (comment != null ? comment.getBytes(UTF_8).length : 0);
+ int expPos = 0;
+ assertEquals("Not at position 0", expPos, view.buffer.position());
+ assertEquals("Not sized correctly", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ }
+ }
+
+ @Test
+ public void testView_ByteBuffer_String() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 100; i++) {
+ buffer.put((byte) i);
+ }
+ int offset = 50;
+ buffer.position(offset);
+ String comment = "this is a comment";
+ EndOfCentralDirectory view = EndOfCentralDirectory.view(buffer, comment);
+ int expMark = (int) ZipInputStream.ENDSIG;
+ int expSize = ZipInputStream.ENDHDR + comment.length();
+ int expPos = 0;
+ assertEquals("not based at current position", expMark, view.get(ENDSIG));
+ assertEquals("Not slice with position 0", expPos, view.buffer.position());
+ assertEquals("Not sized with comment", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ assertEquals("Incorrect comment", comment, view.getComment());
+ }
+
+ @Test
+ public void testCopy() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ EndOfCentralDirectory view = EndOfCentralDirectory.allocate("comment");
+ view.copy(buffer);
+ int expSize = view.getSize();
+ assertEquals("buffer not advanced as expected", expSize, buffer.position());
+ buffer.position(0);
+ EndOfCentralDirectory clone = EndOfCentralDirectory.viewOf(buffer);
+ assertEquals("Fail to copy mark", view.get(ENDSIG), clone.get(ENDSIG));
+ assertEquals("Fail to copy comment", view.getComment(), clone.getComment());
+ }
+
+ @Test
+ public void testWithAndGetMethods() {
+ short cdDisk = (short) 0x36c2;
+ int cdOffset = 0x924ac255;
+ int cdSize = 0x138ca234;
+ short disk = (short) 0x5c12;
+ short local = (short) 0x4ae1;
+ short total = (short) 0x63be;
+ EndOfCentralDirectory view = EndOfCentralDirectory.allocate("Hello World!")
+ .set(ENDDCD, cdDisk)
+ .set(ENDOFF, cdOffset)
+ .set(ENDSIZ, cdSize)
+ .set(ENDDSK, disk)
+ .set(ENDSUB, local)
+ .set(ENDTOT, total);
+ assertEquals("Central directory start disk", cdDisk, view.get(ENDDCD));
+ assertEquals("Central directory file offset", cdOffset, view.get(ENDOFF));
+ assertEquals("Central directory size", cdSize, view.get(ENDSIZ));
+ assertEquals("This disk number", disk, view.get(ENDDSK));
+ assertEquals("Number of records on this disk", local, view.get(ENDSUB));
+ assertEquals("Total number of central directory records", total, view.get(ENDTOT));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/FakeFileSystem.java b/src/test/java/com/google/devtools/build/android/ziputils/FakeFileSystem.java
new file mode 100644
index 0000000000..77b9b3e76f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/FakeFileSystem.java
@@ -0,0 +1,312 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Simple in-memory test file system.
+ */
+class FakeFileSystem extends FileSystem {
+
+ private final Map<String, byte[]> files = new HashMap<>();
+
+ public FakeFileSystem() {
+ FileSystem.fileSystem = this;
+ }
+
+ public void addFile(String name, byte[] content) {
+ files.put(name, content);
+ }
+
+ public void addFile(String name, String content) {
+ files.put(name, content.getBytes(UTF_8));
+ }
+
+ public String content(String filename) throws IOException {
+ byte[] data = files.get(filename);
+ if (data == null) {
+ throw new FileNotFoundException();
+ }
+ return new String(data, UTF_8);
+ }
+
+ public byte[] toByteArray(String filename) throws IOException {
+ byte[] data = files.get(filename);
+ if (data == null) {
+ throw new FileNotFoundException();
+ }
+ return data;
+ }
+
+ @Override
+ public FileChannel getInputChannel(String filename) throws IOException {
+ return new FakeReadChannel(filename);
+ }
+
+ @Override
+ public FileChannel getOutputChannel(String filename, boolean append) throws IOException {
+ return new FakeWriteChannel(filename);
+ }
+
+ @Override
+ public InputStream getInputStream(String filename) throws IOException {
+ byte[] data = files.get(filename);
+ if (data == null) {
+ throw new FileNotFoundException();
+ }
+ return new ByteArrayInputStream(data);
+ }
+
+ class FakeReadChannel extends FileChannel {
+
+ final String name;
+ byte[] data;
+ int position;
+
+ public FakeReadChannel(String filename) throws IOException {
+ this.name = filename;
+ this.data = toByteArray(filename);
+ this.position = 0;
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ if (position >= data.length) {
+ return -1;
+ }
+ int remaining = data.length - position;
+ if (dst.remaining() < remaining) {
+ remaining = dst.remaining();
+ }
+ dst.put(data, position, remaining);
+ position += remaining;
+ return remaining;
+ }
+
+ @Override
+ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long position() throws IOException {
+ return position;
+ }
+
+ @Override
+ public FileChannel position(long newPosition) throws IOException {
+ position = (int) newPosition;
+ return this;
+ }
+
+ @Override
+ public long size() throws IOException {
+ return data.length;
+ }
+
+ @Override
+ public FileChannel truncate(long size) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void force(boolean metaData) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long transferTo(long position, long count, WritableByteChannel target)
+ throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long transferFrom(ReadableByteChannel src, long position, long count)
+ throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public int read(ByteBuffer dst, long position) throws IOException {
+ if (position < 0 || position >= data.length) {
+ throw new IOException("out of bounds");
+ }
+ int remaining = data.length - (int) position;
+ if (dst.remaining() < remaining) {
+ remaining = dst.remaining();
+ }
+ dst.put(data, (int) position, remaining);
+ return remaining;
+ }
+
+ @Override
+ public int write(ByteBuffer src, long position) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
+ throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public ByteBuffer map(long position, long size) {
+ return ByteBuffer.wrap(data, (int) position, (int) size).slice();
+ }
+
+ @Override
+ public FileLock lock(long position, long size, boolean shared) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ protected void implCloseChannel() throws IOException {
+ }
+ }
+
+ class FakeWriteChannel extends FileChannel {
+
+ final String name;
+ final ByteArrayOutputStream buf;
+ int position;
+
+ public FakeWriteChannel(String filename) {
+ this.name = filename;
+ this.buf = new ByteArrayOutputStream();
+ this.position = 0;
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ byte[] bytes = new byte[src.remaining()];
+ src.get(bytes);
+ buf.write(bytes);
+ position += bytes.length;
+ return bytes.length;
+ }
+
+ @Override
+ public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long position() throws IOException {
+ return position;
+ }
+
+ @Override
+ public FileChannel position(long newPosition) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long size() throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public FileChannel truncate(long size) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public void force(boolean metaData) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long transferTo(long position, long count, WritableByteChannel target)
+ throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public long transferFrom(ReadableByteChannel src, long position, long count)
+ throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public int read(ByteBuffer dst, long position) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public int write(ByteBuffer src, long position) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public MappedByteBuffer map(FileChannel.MapMode mode, long position, long size)
+ throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public FileLock lock(long position, long size, boolean shared) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
+ protected void implCloseChannel() throws IOException {
+ files.put(name, buf.toByteArray());
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/FileSystem.java b/src/test/java/com/google/devtools/build/android/ziputils/FileSystem.java
new file mode 100644
index 0000000000..0a29957ed5
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/FileSystem.java
@@ -0,0 +1,78 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.android.ziputils;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
+
+/**
+ * A simple file system abstraction, for testing. This library doesn't itself open files.
+ * Clients are responsible for opening files and pass in file channels to the API.
+ * This class is currently here to be able to use an common abstraction for testing the
+ * library, and tools implemented using this library. This class may be removed in the future.
+ */
+class FileSystem {
+
+ protected static FileSystem fileSystem;
+
+ /**
+ * Returns the configured file system implementation. If no filesystem is configured, and
+ * instance of this class is created and returned. The default filesystem implementation is
+ * a simple wrapper of the standard Java file system.
+ */
+ public static FileSystem fileSystem() {
+ if (fileSystem == null) {
+ fileSystem = new FileSystem();
+ }
+ return fileSystem;
+ }
+
+ /**
+ * Opens a file for reading, and returns a file channel.
+ *
+ * @param filename name of file to open.
+ * @return file channel open for reading.
+ * @throws java.io.IOException
+ */
+ public FileChannel getInputChannel(String filename) throws IOException {
+ return new FileInputStream(filename).getChannel();
+ }
+
+ /**
+ * Opens a file for writing, and returns a file channel.
+ *
+ * @param filename name of file to open.
+ * @param append whether to open file in append mode.
+ * @return file channel open for write.
+ * @throws java.io.IOException
+ */
+ public FileChannel getOutputChannel(String filename, boolean append) throws IOException {
+ return new FileOutputStream(filename, append).getChannel();
+ }
+
+ /**
+ * Opens a file for reading, and returns an input stream.
+ *
+ * @param filename name of file to open.
+ * @return input stream reading from the specified file.
+ * @throws java.io.IOException
+ */
+ public InputStream getInputStream(String filename) throws IOException {
+ return new FileInputStream(filename);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/LocalFileHeaderTest.java b/src/test/java/com/google/devtools/build/android/ziputils/LocalFileHeaderTest.java
new file mode 100644
index 0000000000..cfec2dca84
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/LocalFileHeaderTest.java
@@ -0,0 +1,139 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCCRC;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCFLG;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCHOW;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCLEN;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCSIG;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCSIZ;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCTIM;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCVER;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Unit tests for {@link LocalFileHeader}.
+ */
+@RunWith(JUnit4.class)
+public class LocalFileHeaderTest {
+
+ @Test
+ public void testViewOf() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 100; i++) {
+ buffer.put((byte) i);
+ }
+ int offset = 20;
+ int filenameLength = 10;
+ int extraLength = 25;
+ int marker = LocalFileHeader.SIGNATURE;
+ buffer.putShort(offset + ZipInputStream.LOCNAM, (short) filenameLength); // filename length
+ buffer.putShort(offset + ZipInputStream.LOCEXT, (short) extraLength); // extra data length
+ buffer.putInt(offset, marker); // need to zero filename length to have predictable size
+ buffer.position(offset);
+ LocalFileHeader view = LocalFileHeader.viewOf(buffer);
+ int expMark = (int) ZipInputStream.LOCSIG;
+ int expSize = ZipInputStream.LOCHDR + filenameLength + extraLength; // fixed + comment
+ int expPos = 0;
+ assertEquals("not based at current position", expMark, view.get(LOCSIG));
+ assertEquals("Not slice with position 0", expPos, view.buffer.position());
+ assertEquals("Not sized with comment", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ }
+
+ @Test
+ public void testView_String_byteArr() {
+ String filename = "pkg/foo.class";
+ byte[] extraData = { 1, 2, 3, 4, 5, 6, 7, 8};
+ int expSize = ZipInputStream.LOCHDR + filename.getBytes(UTF_8).length
+ + extraData.length;
+ int expPos = 0;
+ LocalFileHeader view = LocalFileHeader.allocate(filename, extraData);
+ assertEquals("Incorrect filename", filename, view.getFilename());
+ Assert.assertArrayEquals("Incorrect extra data", extraData, view.getExtraData());
+ assertEquals("Not at position 0", expPos, view.buffer.position());
+ assertEquals("Not sized correctly", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ }
+
+ @Test
+ public void testView_3Args() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ for (int i = 0; i < 100; i++) {
+ buffer.put((byte) i);
+ }
+ int offset = 20;
+ buffer.position(offset);
+ String filename = "pkg/foo.class";
+ int expMark = LocalFileHeader.SIGNATURE;
+ int expSize = ZipInputStream.LOCHDR + filename.getBytes(UTF_8).length;
+ int expPos = 0;
+ LocalFileHeader view = LocalFileHeader.view(buffer, filename, null);
+ assertEquals("not based at current position", expMark, view.get(LOCSIG));
+ assertEquals("Not slice with position 0", expPos, view.buffer.position());
+ assertEquals("Not sized with filename", expSize, view.getSize());
+ assertEquals("Not limited to size", expSize, view.buffer.limit());
+ assertEquals("Incorrect filename", filename, view.getFilename());
+ }
+
+ @Test
+ public void testCopy() {
+ ByteBuffer buffer = ByteBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN);
+ LocalFileHeader view = LocalFileHeader.allocate("pkg/foo.class", null);
+ view.copy(buffer);
+ int expSize = view.getSize();
+ assertEquals("buffer not advanced as expected", expSize, buffer.position());
+ buffer.position(0);
+ LocalFileHeader clone = LocalFileHeader.viewOf(buffer);
+ assertEquals("Fail to copy mark", view.get(LOCSIG), view.get(LOCSIG));
+ assertEquals("Fail to copy comment", view.getFilename(), clone.getFilename());
+ }
+
+ @Test
+ public void testWithAndGetMethods() {
+ int crc = 0x12345678;
+ int compressed = 0x357f1d5;
+ int uncompressed = 0x74813159;
+ short flags = 0x7a61;
+ short method = 0x3b29;
+ int time = 0x12c673e1;
+ short version = 0x1234;
+ LocalFileHeader view = LocalFileHeader.allocate("pkg/foo.class", null)
+ .set(LOCCRC, crc)
+ .set(LOCSIZ, compressed)
+ .set(LOCLEN, uncompressed)
+ .set(LOCFLG, flags)
+ .set(LOCHOW, method)
+ .set(LOCTIM, time)
+ .set(LOCVER, version);
+ assertEquals("CRC", crc, view.get(LOCCRC));
+ assertEquals("Compressed size", compressed, view.get(LOCSIZ));
+ assertEquals("Uncompressed size", uncompressed, view.get(LOCLEN));
+ assertEquals("Flags", flags, view.get(LOCFLG));
+ assertEquals("Method", method, view.get(LOCHOW));
+ assertEquals("Modified time", time, view.get(LOCTIM));
+ assertEquals("Version needed", version, view.get(LOCVER));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/ScanUtilTest.java b/src/test/java/com/google/devtools/build/android/ziputils/ScanUtilTest.java
new file mode 100644
index 0000000000..2f6be47fd1
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/ScanUtilTest.java
@@ -0,0 +1,116 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link ScanUtil}.
+ */
+@RunWith(JUnit4.class)
+public class ScanUtilTest {
+
+ @Test
+ public void testScanTo() {
+ try {
+ assertLocation(null, new byte[]{}, -1);
+ fail("No exception on null target");
+ } catch (NullPointerException ex) {
+ // expected
+ }
+ try {
+ assertLocation(new byte[] {}, null, -1);
+ fail("No exception on null domain");
+ } catch (NullPointerException ex) {
+ // expected
+ }
+ assertLocation(new byte[] {}, new byte[] {}, -1);
+ assertLocation(new byte[] {}, new byte[] {1}, 0);
+ assertLocation(new byte[] {}, new byte[] {1, 2, 3, 4}, 0);
+ assertLocation(new byte[] {1}, new byte[] {}, -1);
+ assertLocation(new byte[] {1}, new byte[] {1}, 0);
+ assertLocation(new byte[] {1}, new byte[] {1, 2, 3, 4}, 0);
+ assertLocation(new byte[] {1}, new byte[] {5, 4, 1, 2, 3, 4}, 2);
+ assertLocation(new byte[] {1}, new byte[] {4, 2, 3, 1}, 3);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {}, -1);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {1}, -1);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {1, 2, 3, 4}, 0);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {1, 2, 3, 4, 1, 2, 3, 4}, 0);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 1, 2, 3, 4}, 1);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 1, 2, 3, 4, 5}, 1);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 5, 1, 2, 3, 4}, 2);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 1, 1, 2, 3, 4}, 2);
+ assertLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 1, 2, 3, 5, 4}, -1);
+ }
+
+ @Test
+ public void testScanBackwardsTo() {
+ try {
+ assertBackwardsLocation(null, new byte[]{}, -1);
+ fail("No exception on null target");
+ } catch (NullPointerException ex) {
+ // expected
+ }
+ try {
+ assertBackwardsLocation(new byte[]{}, null, -1);
+ fail("No exception on null domain");
+ } catch (NullPointerException ex) {
+ // expected
+ }
+ assertBackwardsLocation(new byte[] {}, new byte[] {}, -1);
+ assertBackwardsLocation(new byte[] {}, new byte[] {1}, 0);
+ assertBackwardsLocation(new byte[] {}, new byte[] {1, 2, 3, 4}, 3);
+ assertBackwardsLocation(new byte[] {1}, new byte[] {}, -1);
+ assertBackwardsLocation(new byte[] {1}, new byte[] {1}, 0);
+ assertBackwardsLocation(new byte[] {1}, new byte[] {1, 2, 3, 4}, 0);
+ assertBackwardsLocation(new byte[] {1}, new byte[] {5, 4, 1, 2, 3, 4}, 2);
+ assertBackwardsLocation(new byte[] {1}, new byte[] {4, 2, 3, 1}, 3);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {}, -1);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {1}, -1);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {1, 2, 3, 4}, 0);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {1, 2, 3, 4, 1, 2, 3, 4}, 4);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {1, 2, 3, 4, 1, 2, 3, 4, 1}, 4);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 1, 2, 3, 4}, 1);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 1, 2, 3, 4, 5}, 1);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 5, 1, 2, 3, 4}, 2);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 1, 1, 2, 3, 4}, 2);
+ assertBackwardsLocation(new byte[] {1, 2, 3, 4}, new byte[] {5, 1, 2, 3, 5, 4}, -1);
+ }
+
+ private void assertLocation(byte[] target, byte[] domain, int expected) {
+ int pos = ScanUtil.scanTo(target, domain != null ? ByteBuffer.wrap(domain) : null);
+ assertWithMessage("Position of " + Arrays.toString(target) + " in " + Arrays.toString(domain))
+ .that(expected).isEqualTo(pos);
+ }
+
+ private void assertBackwardsLocation(byte[] target, byte[] domain, int expected) {
+ ByteBuffer buf = null;
+ if (domain != null) {
+ buf = ByteBuffer.wrap(domain);
+ buf.position(buf.limit());
+ }
+ int pos = ScanUtil.scanBackwardsTo(target, buf);
+ assertWithMessage("Position of " + Arrays.toString(target) + " in " + Arrays.toString(domain)
+ + ", " + buf.position() + ", " + buf.limit())
+ .that(expected).isEqualTo(pos);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/SplitZipTest.java b/src/test/java/com/google/devtools/build/android/ziputils/SplitZipTest.java
new file mode 100644
index 0000000000..a10c9b11df
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/SplitZipTest.java
@@ -0,0 +1,436 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * Unit tests for {@link SplitZip}.
+ */
+@RunWith(JUnit4.class)
+public class SplitZipTest {
+ private FakeFileSystem fileSystem;
+
+ @Before
+ public void setUp() {
+ fileSystem = new FakeFileSystem();
+ }
+
+ @Test
+ public void test() {
+ SplitZip instance = new SplitZip();
+ assertThat(instance.getMainClassListFile()).isNull();
+ assertThat(instance.isVerbose()).isFalse();
+ assertThat(instance.getEntryDate()).isNull();
+ assertThat(instance.getResourceFile()).isNull();
+ }
+
+ @Test
+ public void testSetOutput() {
+ SplitZip instance = new SplitZip();
+ try {
+ instance.addOutput((String) null);
+ fail("should have failed");
+ } catch (Exception ex) {
+ assertTrue("NullPointerException expected", ex instanceof NullPointerException);
+ }
+ try {
+ SplitZip result = instance
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard1.jar", false),
+ "out/shard1.jar"))
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard2.jar", false),
+ "out/shard2.jar"));
+ assertSame(instance, result);
+ } catch (IOException ex) {
+ fail("Unexpected exception: " + ex);
+ }
+ }
+
+ @Test
+ public void testSetResourceFile() {
+ SplitZip instance = new SplitZip();
+ String res = "res";
+ SplitZip result = instance.setResourceFile(res);
+ assertSame(instance, result);
+ }
+
+ @Test
+ public void testGetResourceFile() {
+ SplitZip instance = new SplitZip();
+ String res = "res";
+ assertThat(instance.setResourceFile(res).getResourceFile()).isEqualTo(res);
+ assertThat(instance.setResourceFile((String) null).getResourceFile()).isNull();
+ }
+
+ @Test
+ public void testSetMainClassListFile() {
+ SplitZip instance = new SplitZip();
+ SplitZip result = instance.setMainClassListFile((String) null);
+ assertSame(instance, result);
+ result = instance.setMainClassListFile("no format checks");
+ assertSame(instance, result);
+ }
+
+ @Test
+ public void testGetMainClassListFile() {
+ SplitZip instance = new SplitZip();
+ String file = "list.txt";
+ instance.setMainClassListFile(file);
+ String result = instance.getMainClassListFile();
+ assertThat(result).isEqualTo(file);
+ }
+
+ // Instance date test. Implementation has little constraints today.
+ // This should be improved.
+ @Test
+ public void testSetEntryDate() {
+ SplitZip instance = new SplitZip();
+ SplitZip result = instance.setEntryDate(null);
+ assertSame(instance, result);
+ }
+
+ @Test
+ public void testGetEntryDate() {
+ SplitZip instance = new SplitZip();
+ Date now = new Date();
+ instance.setEntryDate(now);
+ Date result = instance.getEntryDate();
+ assertSame(result, now);
+ instance.setEntryDate(null);
+ assertThat(instance.getEntryDate()).isNull();
+ }
+
+ @Test
+ public void testUseDefaultEntryDate() {
+ SplitZip instance = new SplitZip();
+ SplitZip result = instance.useDefaultEntryDate();
+ assertSame(instance, result);
+ Date date = instance.getEntryDate();
+ assertThat(date).isEqualTo(DosTime.DOS_EPOCH);
+ }
+
+ @Test
+ public void testAddInput() {
+ try {
+ SplitZip instance = new SplitZip();
+ String noexists = "noexists.zip";
+ instance.addInput(noexists);
+ fail("should not be able to add non existing file: " + noexists);
+ } catch (IOException ex) {
+ assertTrue("FileNotFoundException expected", ex instanceof FileNotFoundException);
+ }
+ }
+
+ @Test
+ public void testAddInputs() {
+ try {
+ SplitZip instance = new SplitZip();
+ String noexists = "noexists.zip";
+ instance.addInputs(Arrays.asList(noexists));
+ fail("should not be able to add non existing file: " + noexists);
+ } catch (IOException ex) {
+ assertTrue("FileNotFoundException expected", ex instanceof FileNotFoundException);
+ }
+ }
+
+ @Test
+ public void testCopyOneDir() {
+ try {
+ new ZipFileBuilder()
+ .add("pkg/test.txt", "hello world")
+ .create("input.zip");
+ byte[] inputBytes = fileSystem.toByteArray("input.zip");
+
+ new SplitZip()
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard1.jar", false),
+ "out/shard1.jar"))
+ .setVerbose(true)
+ .addInput(new ZipIn(fileSystem.getInputChannel("input.zip"), "input.zip"))
+ .run()
+ .close();
+
+ byte[] outputBytes = fileSystem.toByteArray("out/shard1.jar");
+ assertThat(inputBytes).isEqualTo(outputBytes);
+ } catch (IOException e) {
+ fail("Exception: " + e);
+ }
+ }
+
+ @Test
+ public void testSetDate() {
+ try {
+ Date now = new Date();
+ new ZipFileBuilder()
+ .add(new ZipFileBuilder.FileInfo("pkg/test.txt", new DosTime(now).time, "hello world"))
+ .create("input.zip");
+
+ new ZipFileBuilder()
+ .add(new ZipFileBuilder.FileInfo("pkg/test.txt", DosTime.EPOCH.time, "hello world"))
+ .create("expect.zip");
+ byte[] expectBytes = fileSystem.toByteArray("expect.zip");
+
+ new SplitZip()
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard1.jar", false),
+ "out/shard1.jar"))
+ .setVerbose(true)
+ .setEntryDate(DosTime.DOS_EPOCH)
+ .addInput(new ZipIn(fileSystem.getInputChannel("input.zip"), "input.zip"))
+ .run()
+ .close();
+
+ byte[] outputBytes = fileSystem.toByteArray("out/shard1.jar");
+ assertThat(expectBytes).isEqualTo(outputBytes);
+ } catch (IOException e) {
+ fail("Exception: " + e);
+ }
+ }
+
+ @Test
+ public void testDuplicatedInput() {
+ try {
+ new ZipFileBuilder()
+ .add("pkg/test.txt", "hello world")
+ .create("input1.zip");
+
+ new ZipFileBuilder()
+ .add("pkg/test.txt", "Goodbye world")
+ .create("input2.zip");
+
+ new SplitZip()
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard1.jar", false),
+ "out/shard1.jar"))
+ .setVerbose(true)
+ .addInput(new ZipIn(fileSystem.getInputChannel("input1.zip"), "input1.zip"))
+ .addInput(new ZipIn(fileSystem.getInputChannel("input2.zip"), "input2.zip"))
+ .run()
+ .close();
+
+ new ZipFileBuilder()
+ .add("pkg/test.txt", "hello world")
+ .create("expect.zip");
+ byte[] expectBytes = fileSystem.toByteArray("expect.zip");
+ byte[] outputBytes = fileSystem.toByteArray("out/shard1.jar");
+ assertThat(expectBytes).isEqualTo(outputBytes);
+ } catch (IOException e) {
+ fail("Exception: " + e);
+ }
+ }
+
+ @Test
+ public void testCopyThreeDir() {
+ try {
+ new ZipFileBuilder()
+ .add("pkg/hello.txt", "hello world")
+ .add("pkg/greet.txt", "how are you")
+ .add("pkg/bye.txt", "bye bye")
+ .create("input.zip");
+ byte[] inputBytes = fileSystem.toByteArray("input.zip");
+
+ new SplitZip()
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard1.jar", false),
+ "out/shard1.jar"))
+ .setVerbose(true)
+ .addInput(new ZipIn(fileSystem.getInputChannel("input.zip"), "input.zip"))
+ .run()
+ .close();
+
+ byte[] outputBytes = fileSystem.toByteArray("out/shard1.jar");
+ assertThat(inputBytes).isEqualTo(outputBytes);
+ } catch (IOException e) {
+ fail("Exception: " + e);
+ }
+ }
+
+ @Test
+ public void testSplitInTwo() {
+ try {
+ new ZipFileBuilder()
+ .add("pkg1/test1.class", "hello world")
+ .add("pkg2/test1.class", "hello world")
+ .add("pkg1/test2.class", "how are you")
+ .add("pkg2/test2.class", "how are you")
+ .add("pkg1/test3.class", "bye bye")
+ .add("pkg2/test3.class", "bye bye")
+ .create("input.jar");
+
+ new SplitZip()
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard1.jar", false),
+ "out/shard1.jar"))
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard2.jar", false),
+ "out/shard2.jar"))
+ .setVerbose(true)
+ .addInput(new ZipIn(fileSystem.getInputChannel("input.jar"), "input.jar"))
+ .run()
+ .close();
+
+ new ZipFileBuilder()
+ .add("pkg1/test1.class", "hello world")
+ .add("pkg1/test2.class", "how are you")
+ .add("pkg1/test3.class", "bye bye")
+ .create("expected/shard1.jar");
+ new ZipFileBuilder()
+ .add("pkg2/test1.class", "hello world")
+ .add("pkg2/test2.class", "how are you")
+ .add("pkg2/test3.class", "bye bye")
+ .create("expected/shard2.jar");
+
+ assertThat(fileSystem.toByteArray("out/shard1.jar"))
+ .isEqualTo(fileSystem.toByteArray("expected/shard1.jar"));
+
+ assertThat(fileSystem.toByteArray("out/shard2.jar"))
+ .isEqualTo(fileSystem.toByteArray("expected/shard2.jar"));
+
+ } catch (IOException e) {
+ fail("Exception: " + e);
+ }
+ }
+
+ @Test
+ public void testSeparateResources() {
+ try {
+ new ZipFileBuilder()
+ .add("resources/oil.xml", "oil")
+ .add("pkg1/test1.class", "hello world")
+ .add("pkg2/test1.class", "hello world")
+ .add("pkg1/test2.class", "how are you")
+ .add("pkg2/test2.class", "how are you")
+ .add("pkg1/test3.class", "bye bye")
+ .add("pkg2/test3.class", "bye bye")
+ .create("input.jar");
+ ZipIn input = new ZipIn(fileSystem.getInputChannel("input.jar"), "input.jar");
+
+ String resources = "out/resources.zip";
+ ZipOut resourceOut = new ZipOut(fileSystem.getOutputChannel(resources, false), resources);
+ new SplitZip()
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard1.jar", false),
+ "out/shard1.jar"))
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard2.jar", false),
+ "out/shard2.jar"))
+ .setResourceFile(resourceOut)
+ .setVerbose(true)
+ .addInput(input)
+ .run()
+ .close();
+
+ new ZipFileBuilder()
+ .add("pkg1/test1.class", "hello world")
+ .add("pkg1/test2.class", "how are you")
+ .add("pkg1/test3.class", "bye bye")
+ .create("expected/shard1.jar");
+ new ZipFileBuilder()
+ .add("pkg2/test1.class", "hello world")
+ .add("pkg2/test2.class", "how are you")
+ .add("pkg2/test3.class", "bye bye")
+ .create("expected/shard2.jar");
+ new ZipFileBuilder()
+ .add("resources/oil.xml", "oil")
+ .create("expected/resources.zip");
+
+
+ assertThat(fileSystem.toByteArray("out/shard1.jar"))
+ .isEqualTo(fileSystem.toByteArray("expected/shard1.jar"));
+
+ assertThat(fileSystem.toByteArray("out/shard2.jar"))
+ .isEqualTo(fileSystem.toByteArray("expected/shard2.jar"));
+
+ assertThat(fileSystem.toByteArray("out/resources.zip"))
+ .isEqualTo(fileSystem.toByteArray("expected/resources.zip"));
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail("Exception: " + e);
+ }
+ }
+
+ @Test
+ public void testMainClassListFile() {
+ SplitZip instance = new SplitZip();
+ String filename = "x/y/z/foo.txt";
+ instance.setMainClassListFile(filename);
+ String out = instance.getMainClassListFile();
+ assertThat(out).isEqualTo(filename);
+
+ instance.setMainClassListFile((String) null);
+ assertThat(instance.getMainClassListFile()).isNull();
+
+ try {
+ new ZipFileBuilder()
+ .add("pkg1/test1.class", "hello world")
+ .add("pkg2/test1.class", "hello world")
+ .add("pkg1/test2.class", "how are you")
+ .add("pkg2/test2.class", "how are you")
+ .add("pkg1/test3.class", "bye bye")
+ .add("pkg2/test3.class", "bye bye")
+ .create("input.jar");
+
+ String classFileList = "pkg1/test1.class\npkg2/test2.class\n";
+ fileSystem.addFile("main_dex_list.txt", classFileList);
+
+ new SplitZip()
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard1.jar", false),
+ "out/shard1.jar"))
+ .addOutput(new ZipOut(fileSystem.getOutputChannel("out/shard2.jar", false),
+ "out/shard2.jar"))
+ .setMainClassListFile(fileSystem.getInputStream("main_dex_list.txt"))
+ .addInput(new ZipIn(fileSystem.getInputChannel("input.jar"), "input.jar"))
+ .run()
+ .close();
+
+ new ZipFileBuilder()
+ .add("pkg1/test1.class", "hello world")
+ .add("pkg2/test2.class", "how are you")
+ .create("expected/shard1.jar");
+
+ // Sorting is used for split calculation, but classes assigned to the same shard are expected
+ // to be output in the order they appear in input.
+ new ZipFileBuilder()
+ .add("pkg2/test1.class", "hello world")
+ .add("pkg1/test2.class", "how are you")
+ .add("pkg1/test3.class", "bye bye")
+ .add("pkg2/test3.class", "bye bye")
+ .create("expected/shard2.jar");
+
+ assertThat(fileSystem.toByteArray("out/shard1.jar"))
+ .isEqualTo(fileSystem.toByteArray("expected/shard1.jar"));
+
+ assertThat(fileSystem.toByteArray("out/shard2.jar"))
+ .isEqualTo(fileSystem.toByteArray("expected/shard2.jar"));
+
+ } catch (IOException e) {
+ fail("Exception: " + e);
+ }
+ }
+
+ @Test
+ public void testVerbose() {
+ SplitZip instance = new SplitZip();
+ instance.setVerbose(true);
+ assertThat(instance.isVerbose()).isTrue();
+ instance.setVerbose(false);
+ assertThat(instance.isVerbose()).isFalse();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/SplitterTest.java b/src/test/java/com/google/devtools/build/android/ziputils/SplitterTest.java
new file mode 100644
index 0000000000..7b791c0f75
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/SplitterTest.java
@@ -0,0 +1,345 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Range;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link Splitter}.
+ */
+@RunWith(JUnit4.class)
+public class SplitterTest {
+
+ private static final String ARCHIVE_DIR_SUFFIX = "/";
+ private static final String ARCHIVE_FILE_SEPARATOR = "/";
+ private static final String CLASS_SUFFIX = ".class";
+
+ @Test
+ public void testAssign() {
+
+ int size = 10;
+
+ Collection<String> input;
+ ArrayList<String> filter;
+ Map<String, Integer> output;
+
+ input = genEntries(size, size);
+ filter = new ArrayList<>(10);
+ for (int i = 0; i < size; i++) {
+ filter.add("dir" + i + ARCHIVE_FILE_SEPARATOR + "file" + i + CLASS_SUFFIX);
+ }
+ Splitter splitter = new Splitter(size + 1, input.size());
+ splitter.assign(filter);
+ splitter.nextShard();
+ output = new LinkedHashMap<>();
+ for (String path : input) {
+ output.put(path, splitter.assign(path));
+ }
+
+ for (int i = 0; i < size; i++) {
+ for (int j = 0; j < size; j++) {
+ String path = "dir" + i + ARCHIVE_FILE_SEPARATOR + "file" + j + CLASS_SUFFIX;
+ if (i == j) {
+ assertThat(output.get(path)).isEqualTo(0);
+ } else {
+ assertThat(output.get(path)).isEqualTo(i + 1);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Test splitting of single-ile packages. Note, this is also testing for the situation
+ * where input entries are unordered, and thus appearing to be in different packages,
+ * to the implementation that only confiders the previous file to determine package
+ * boundaries.
+ */
+ @Test
+ public void testSingleFilePackages() {
+ int[][] params = {
+ { 1, 1, 1}, // one shard, for one package with one file
+ {1, 2, 1}, // one shard, for two packages, with one file each
+ {1, 10, 1}, // one shard, for ten packages, with one file each
+ {2, 2, 1}, // ...
+ {2, 10, 1},
+ {2, 100, 1},
+ {10, 10, 1},
+ {10, 15, 1},
+ {10, 95, 1},
+ {97, 10000, 1},
+ };
+ comboRunner(params);
+ }
+
+ /**
+ * Test cases where the number of shards is less than the number
+ * of packages. This implies that the package size is less than
+ * the average shard size. We expect shards to be multiple of
+ * package size.
+ */
+ @Test
+ public void testPackageSplit() {
+ int[][] params = {
+ {2, 3, 2}, // two shards, for three packages, with two files each
+ {2, 3, 9}, // ...
+ {2, 3, 10},
+ {2, 3, 11},
+ {2, 3, 19},
+
+ {2, 10, 2},
+ {2, 10, 9},
+ {2, 10, 10},
+ {2, 10, 11},
+ {2, 10, 19},
+
+ {10, 11, 2},
+ {10, 11, 9},
+ {10, 11, 10},
+ {10, 11, 11},
+ {10, 11, 19},
+
+ {10, 111, 2},
+ {10, 111, 9},
+ {10, 111, 10},
+ {10, 111, 11},
+ {10, 111, 19},
+
+ {25, 1000, 8},
+ {25, 1000, 10},
+ {25, 1000, 19},
+
+ {250, 10000, 19},
+ };
+ comboRunner(params);
+ }
+
+ /**
+ * Tests situations where the number of shards exceeds the number of
+ * packages (but not the number of files). That is, the implementation
+ * must split at least one package.
+ */
+ @Test
+ public void testForceSplit() {
+ int[][] params = {
+ {2, 1, 2}, // two shards, for one package, with two files
+ {2, 1, 9}, // ...
+ {2, 1, 10},
+ {2, 1, 11},
+
+ {3, 2, 2},
+ {10, 9, 2},
+ {10, 2, 9},
+ {10, 9, 9},
+ {10, 2, 10},
+ {10, 9, 10},
+ {10, 2, 11},
+ {10, 9, 11},
+ {10, 2, 111},
+ {10, 9, 111},
+
+ {100, 12, 9},
+ {100, 12, 9},
+ {100, 10, 10},
+ {100, 10, 10},
+ {100, 10, 11},
+ {100, 20, 111},
+ };
+ comboRunner(params);
+ }
+
+ /**
+ * Tests situation where the number of shards requested exceeds the
+ * the number of files. Empty shards are expected.
+ */
+ @Test
+ public void testEmptyShards() {
+ int[][] params = {
+ {2, 1, 1}, // two shards, for one package, with one files
+ {10, 2, 2},
+ {100, 10, 9},
+ {100, 9, 10},
+ };
+ comboRunner(params);
+ }
+
+ /**
+ * Run multiple test for sets of test specifications consisting of
+ * "number of shards", "number of packages", "package size".
+ */
+ private void comboRunner(int[][] params) {
+
+ Collection<String> input;
+ Map<String, Integer> output;
+
+ for (int[] param : params) {
+ input = genEntries(param[1], param[2]);
+ output = runOne(param[0], input);
+ splitAsserts(param[0], param[1], param[2],
+ commonAsserts(param[0], param[1], param[2], input, output));
+ }
+ }
+
+ private Map<String, Integer> runOne(int shards, Collection<String> entries) {
+ Splitter splitter = new Splitter(shards, entries.size());
+ Map<String, Integer> result = new LinkedHashMap<>();
+ for (String entry : entries) {
+ result.put(entry, splitter.assign(entry));
+ }
+ return result;
+ }
+
+ private Collection<String> genEntries(int packages, int files) {
+ List<String> entries = new ArrayList<>();
+ for (int dir = 0; dir < packages; dir++) {
+ for (int file = 0; file < files; file++) {
+ entries.add("dir" + dir + ARCHIVE_FILE_SEPARATOR + "file" + file + CLASS_SUFFIX);
+ }
+ }
+ return entries;
+ }
+
+ private int[] assertAndCountMappings(int shards, int packageSize,
+ Map<String, Integer> output) {
+ int[] counts = new int[shards + 1];
+ String prevPath = null;
+ int prev = -2;
+ for (Map.Entry<String, Integer> entry : output.entrySet()) {
+ String path = entry.getKey();
+ int assignment = entry.getValue();
+ assertThat(assignment).isIn(Range.closed(0, shards));
+ counts[assignment + 1]++;
+ if (path.endsWith(CLASS_SUFFIX)) {
+ if (prev == -2) {
+ assertThat(assignment).isEqualTo(0);
+ } else if (prev > 0 && prev != assignment) {
+ String prevDir = prevPath.substring(0, prevPath.lastIndexOf(ARCHIVE_DIR_SUFFIX));
+ String dir = path.substring(0, path.lastIndexOf(ARCHIVE_DIR_SUFFIX));
+ assertThat(assignment).isEqualTo(prev + 1); // shard index increasing
+ // package boundary, or partial package
+ assertThat(!prevDir.equals(dir) || counts[prev + 1] % packageSize != 0).isTrue();
+ }
+ prevPath = path;
+ }
+ prev = assignment;
+ }
+ return counts;
+ }
+
+ /**
+ * Validate that generated mapping maintains input order.
+ */
+ private void assertMaintainOrder(Collection<String> input, Map<String, Integer> output) {
+ assertThat(output.keySet()).containsExactlyElementsIn(input).inOrder();
+ }
+
+ /**
+ * Verifies that packages have not been unnecessarily split.
+ */
+ private void assertNoSplit(int packageSize, int[] counts) {
+ for (int i = 1; i < counts.length; i++) {
+ assertThat(counts[i] % packageSize).isEqualTo(0);
+ }
+ }
+
+ /**
+ * Verifies the presence of package-split in the tailing shards.
+ */
+ private void assertHasSplit(int packageSize, int[] counts) {
+ for (int i = 1; i < counts.length - 1; i++) {
+ if (counts[i + 1] <= 1) {
+ continue;
+ }
+ assertThat(counts[i] % packageSize).isEqualTo(0);
+ }
+ }
+
+ /**
+ * Verify the presence of tailing empty shards, if unavoidable.
+ */
+ private void assertHasEmpty(int[] counts, boolean expectEmpty) {
+ boolean hasEmpty = false;
+ for (int i = 1; i < counts.length; i++) {
+ if (counts[i] == 0) {
+ hasEmpty = true;
+ } else {
+ assertThat(!hasEmpty || counts[i] == 0).isTrue();
+ }
+ }
+ assertThat(hasEmpty).isEqualTo(expectEmpty);
+ }
+
+ /**
+ * Validates that each chard meets expected minimal and maximum size requirements,
+ * to ensure that shards are reasonably evenly sized.
+ */
+ private void assertBalanced(int shards, int packageCount, int packageSize, int entries,
+ int[] counts) {
+ int classes = packageSize * packageCount;
+ int noneClass = entries - counts[0] - classes;
+ int idealSize = Math.max(1, classes / shards);
+ int superSize = Math.max(1, entries / shards);
+ int almostFull = Math.min(Math.min(10, (idealSize + 3) >> 2), (int) Math.log(shards));
+ int lowerBound = idealSize - almostFull;
+ int upperBound = superSize + Math.max(packageSize, (int) (Math.log(shards)) * 10);
+ for (int i = 1; i < counts.length; i++) {
+ int adjusted = i == 1 ? counts[i] - noneClass : counts[i];
+ if (i < shards && counts[i + 1] > 1) {
+ assertThat(counts[i]).isIn(Range.closed(packageSize, entries));
+ if (noneClass == 0 && counts[0] == 0) {
+ assertThat(counts[i]).isIn(Range.closed(lowerBound, entries));
+ }
+ }
+ assertThat(adjusted).isIn(Range.closed(0, upperBound));
+ }
+ }
+
+ /**
+ * Verifies that packages are only split as expected, and that no unexpected
+ * empty shards are generated.
+ */
+ private void splitAsserts(int shards, int packageCount, int packageSize, int[] counts) {
+ boolean emptyExpected = packageCount * packageSize < shards;
+ boolean splitExpected = shards > packageCount;
+ if (splitExpected) {
+ assertHasSplit(packageSize, counts);
+ } else {
+ assertNoSplit(packageSize, counts);
+ }
+ assertHasEmpty(counts, emptyExpected);
+ }
+
+ /**
+ * Checks assert applicable to all tests.
+ */
+ private int[] commonAsserts(int shards, int packageCount, int packageSize,
+ Collection<String> input, Map<String, Integer> output) {
+ assertMaintainOrder(input, output);
+ int[] counts = assertAndCountMappings(shards, packageSize, output);
+ assertBalanced(shards, packageCount, packageSize, input.size(), counts);
+ return counts;
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/ViewTest.java b/src/test/java/com/google/devtools/build/android/ziputils/ViewTest.java
new file mode 100644
index 0000000000..8eeabbab90
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/ViewTest.java
@@ -0,0 +1,191 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Unit tests for {@link View}.
+ */
+@RunWith(JUnit4.class)
+public class ViewTest {
+
+ private static final FakeFileSystem fileSystem = new FakeFileSystem();
+
+ @Test
+ public void testView() {
+ // View takes ownership of constructor argument!
+ // Subclasses are responsible for slicing, when needed.
+ ByteBuffer buffer = ByteBuffer.allocate(100);
+ TestView instance = new TestView(buffer);
+ buffer.putInt(12345678);
+ int fromBuf = buffer.getInt(0);
+ int fromView = instance.getInt(0);
+ assertEquals("must assume buffer ownership", fromBuf, fromView);
+ int posBuf = buffer.position();
+ int posView = instance.buffer.position();
+ assertEquals("must assume buffer ownership", posBuf, posView);
+ }
+
+ @Test
+ public void testAt() {
+ long fileOffset = 0L;
+ ByteBuffer buffer = ByteBuffer.allocate(100);
+ TestView instance = new TestView(buffer);
+ View<TestView> result = instance.at(fileOffset);
+ assertSame("didn't return this", instance, result);
+
+ long resultValue = instance.fileOffset();
+ assertEquals("didn't return set value", fileOffset, resultValue);
+ }
+
+ @Test
+ public void testFileOffset() {
+ ByteBuffer buffer = ByteBuffer.allocate(100);
+ TestView instance = new TestView(buffer);
+ long expResult = -1L;
+ long result = instance.fileOffset();
+ assertEquals("default file offset should be -1", expResult, result);
+ }
+
+ @Test
+ public void testFinish() {
+ ByteBuffer buffer = ByteBuffer.allocate(100);
+ TestView instance = new TestView(buffer);
+ int limit = instance.buffer.limit();
+ int pos = instance.buffer.position();
+ assertEquals("initial limit", 100, limit);
+ assertEquals("initial position", 0, pos);
+ instance.putInt(1234);
+ limit = instance.buffer.limit();
+ pos = instance.buffer.position();
+ assertEquals("limit unchanged", 100, limit);
+ assertEquals("position advanced", 4, pos);
+ instance.buffer.flip();
+ int finishedLimit = instance.buffer.limit();
+ int finishedPos = instance.buffer.position();
+ assertEquals("must set limit to position", pos, finishedLimit);
+ assertEquals("must set position to 0", 0, finishedPos);
+ }
+
+ @Test
+ public void testWriteTo() throws Exception {
+ FileChannel file = fileSystem.getOutputChannel("hello", false);
+ byte[] bytes = "hello world".getBytes(UTF_8);
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ TestView instance = new TestView(buffer);
+ int expResult = bytes.length;
+ instance.buffer.rewind();
+ int result = file.write(instance.buffer);
+ file.close();
+ assertEquals("incorrect number of bytes written", expResult, result);
+ byte[] bytesWritten = fileSystem.toByteArray("hello");
+ Assert.assertArrayEquals("incorrect bytes written", bytes, bytesWritten);
+ }
+
+ @Test
+ public void testGetBytes() {
+ int off = 3;
+ int len = 5;
+ byte[] bytes = "hello world".getBytes(UTF_8);
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ TestView instance = new TestView(buffer);
+ byte[] expResult = "lo wo".getBytes(UTF_8);
+ byte[] result = instance.getBytes(off, len);
+ assertArrayEquals("incorrect bytes returned", expResult, result);
+ try {
+ instance.getBytes(bytes.length - len + 1, len);
+ fail("expected Exception");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ instance.getBytes(-1, len);
+ fail("expected Exception");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetString() {
+ int off = 6;
+ int len = 5;
+ byte[] bytes = "hello world".getBytes(UTF_8);
+ ByteBuffer buffer = ByteBuffer.wrap(bytes);
+ TestView instance = new TestView(buffer);
+ String expResult = "world";
+ String result = instance.getString(off, len);
+ assertEquals("didn't return this", expResult, result);
+ try {
+ instance.getString(off + 1, len);
+ fail("expected Exception");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ try {
+ instance.getString(-1, len);
+ fail("expected Exception");
+ } catch (IndexOutOfBoundsException ex) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testByteOrder() {
+ byte[] bytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ TestView instance = new TestView(ByteBuffer.wrap(bytes));
+ int expValue = 0x08070605;
+ int value = instance.getInt(4);
+ assertEquals("Byte order incorrect", expValue, value);
+ }
+
+ static class TestView extends View<TestView> {
+ TestView(ByteBuffer buffer) {
+ super(buffer);
+ }
+
+ // Will advance buffer position
+ public void putInt(int value) {
+ buffer.putInt(value);
+ }
+
+ // Will advance buffer position
+ public int getInt() {
+ return buffer.getInt();
+ }
+
+ // will not advance buffer position
+ public void putInt(int index, int value) {
+ buffer.putInt(index, value);
+ }
+
+ // will not advance buffer position
+ public int getInt(int index) {
+ return buffer.getInt(index);
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/ZipFileBuilder.java b/src/test/java/com/google/devtools/build/android/ziputils/ZipFileBuilder.java
new file mode 100644
index 0000000000..ae574c824e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/ZipFileBuilder.java
@@ -0,0 +1,164 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.devtools.build.android.ziputils.DataDescriptor.EXTLEN;
+import static com.google.devtools.build.android.ziputils.DataDescriptor.EXTSIZ;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENFLG;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENLEN;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENOFF;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENSIZ;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENTIM;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCFLG;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCLEN;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCSIZ;
+import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCTIM;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Zip file builder for testing, For now it only supports building
+ * valid zip files.
+ */
+class ZipFileBuilder {
+ private final List<FileInfo> input;
+ private static final Charset CHARSET = Charset.forName("UTF-8");
+ private static final byte[] EMPTY = {};
+
+ public ZipFileBuilder() {
+ input = new ArrayList<>();
+ }
+
+ public ZipFileBuilder add(String filename, String content) {
+ input.add(new FileInfo(filename, content.getBytes(Charset.defaultCharset())));
+ return this;
+ }
+
+ public ZipFileBuilder add(FileInfo fileInfo) {
+ input.add(fileInfo);
+ return this;
+ }
+
+ public void create(String filename) throws IOException {
+ ZipOut out = new ZipOut(FileSystem.fileSystem().getOutputChannel(filename, false), filename);
+ for (FileInfo info : input) {
+ int compressed = info.compressedSize();
+ int uncompressed = info.uncompressedSize();
+ int dirCompressed = info.dirCompressedSize();
+ int dirUncompressed = info.dirUncompressedSize();
+ short flags = info.flags();
+ DirectoryEntry entry = DirectoryEntry.allocate(info.name, info.extra, info.comment);
+ out.nextEntry(entry)
+ .set(CENOFF, out.fileOffset())
+ .set(CENFLG, flags)
+ .set(CENTIM, info.date)
+ .set(CENLEN, dirUncompressed)
+ .set(CENSIZ, dirCompressed);
+ LocalFileHeader header = LocalFileHeader.allocate(info.name, null)
+ .set(LOCFLG, flags)
+ .set(LOCTIM, info.date)
+ .set(LOCLEN, uncompressed)
+ .set(LOCSIZ, compressed);
+ out.write(header);
+ out.write(ByteBuffer.wrap(info.data));
+ if (flags != 0) {
+ DataDescriptor desc = DataDescriptor.allocate()
+ .set(EXTLEN, dirUncompressed)
+ .set(EXTSIZ, dirCompressed);
+ out.write(desc);
+ }
+ }
+ out.close();
+ }
+
+ public static class FileInfo {
+ private final String name;
+ private final short method;
+ private final int date;
+ private final int uncompressed;
+ private final byte[] data;
+ private final byte[] extra;
+ private final String comment;
+ boolean maskSize;
+
+ static final short STORED = 0;
+ static final short DEFLATED = 8;
+
+ public FileInfo(String filename, String content) {
+ this(filename, DosTime.EPOCH.time, STORED, 0,
+ (content == null ? EMPTY : content.getBytes(CHARSET)), null, null);
+ }
+
+ public FileInfo(String filename, byte[] data) {
+ this(filename, DosTime.EPOCH.time, STORED, 0, data, null, null);
+ }
+
+ public FileInfo(String filename, byte[] data, int uncompressed) {
+ this(filename, DosTime.EPOCH.time, DEFLATED, uncompressed, data, null, null);
+ }
+
+ public FileInfo(String filename, int dosTime, String content) {
+ this(filename, dosTime, STORED, 0,
+ (content == null ? EMPTY : content.getBytes(CHARSET)), null, null);
+ }
+
+ public FileInfo(String filename, int dosTime, byte[] data) {
+ this(filename, dosTime, STORED, 0, data, null, null);
+ }
+
+ public FileInfo(String filename, int dosTime, byte[] data, int uncompressed) {
+ this(filename, dosTime, DEFLATED, uncompressed, data, null, null);
+ }
+
+ public FileInfo(String filename, int dosTime, short method, int uncompressed,
+ byte[] content, byte[] extra, String comment) {
+ this.name = filename;
+ this.date = dosTime;
+ this.method = method;
+ this.uncompressed = uncompressed;
+ this.data = content;
+ this.extra = extra;
+ this.comment = comment;
+ maskSize = false;
+ }
+
+ public void setMaskSize(boolean ignore) {
+ maskSize = ignore;
+ }
+
+ int compressedSize() {
+ return method != 0 && !maskSize ? data.length : 0;
+ }
+
+ int uncompressedSize() {
+ return method == 0 ? data.length : maskSize ? 0 : uncompressed;
+ }
+
+ int dirCompressedSize() {
+ return method == 0 ? 0 : data.length;
+ }
+
+ int dirUncompressedSize() {
+ return method == 0 ? data.length : uncompressed;
+ }
+
+ short flags() {
+ return method != 0 && uncompressed == 0 ? LocalFileHeader.SIZE_MASKED_FLAG : 0;
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/ZipInTest.java b/src/test/java/com/google/devtools/build/android/ziputils/ZipInTest.java
new file mode 100644
index 0000000000..9ce75d528c
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/ZipInTest.java
@@ -0,0 +1,558 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENHOW;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENLEN;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENOFF;
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENSIZ;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDOFF;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSIG;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSIZ;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDSUB;
+import static com.google.devtools.build.android.ziputils.EndOfCentralDirectory.ENDTOT;
+import static com.google.devtools.build.android.ziputils.ZipIn.ZipEntry;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Unit tests for {@link ZipIn}.
+ */
+@RunWith(JUnit4.class)
+public class ZipInTest {
+
+ private static final int ENTRY_COUNT = 1000;
+ private FakeFileSystem fileSystem;
+
+ @Before
+ public void setUp() throws Exception {
+ fileSystem = new FakeFileSystem();
+ }
+
+ /**
+ * Test of endOfCentralDirectory method, of class ZipIn.
+ */
+ @Test
+ public void testEndOfCentralDirectory() throws Exception {
+
+ String filename = "test.zip";
+ byte[] bytes;
+ ByteBuffer buffer;
+ String comment;
+ int commentLen;
+ int offset;
+ ZipIn zipIn;
+ EndOfCentralDirectory result;
+ String subcase;
+
+ // Find it, even if it's the only useful thing in the file.
+ subcase = " EOCD found it, ";
+ bytes = new byte[] {
+ 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ fileSystem.addFile(filename, bytes);
+ zipIn = newZipIn(filename);
+ result = zipIn.endOfCentralDirectory();
+ assertNotNull(subcase + "found", result);
+
+ subcase = " EOCD not there at all, ";
+ bytes = new byte[]{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ fileSystem.addFile(filename, bytes);
+ zipIn = newZipIn(filename);
+ try {
+ zipIn.endOfCentralDirectory();
+ fail(subcase + "expected IllegalStateException");
+ } catch (Exception ex) {
+ assertSame(subcase + "caught exception", IllegalStateException.class, ex.getClass());
+ }
+
+ // If we can't read it, it's not there
+ subcase = " EOCD too late to read, ";
+ bytes = new byte[] {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ fileSystem.addFile(filename, bytes);
+ zipIn = newZipIn(filename);
+ try {
+ zipIn.endOfCentralDirectory();
+ fail(subcase + "expected IndexOutOfBoundsException");
+ } catch (Exception ex) {
+ assertSame(subcase + "caught exception", IndexOutOfBoundsException.class, ex.getClass());
+ }
+
+ // Current implementation doesn't know to scan past a bad EOCD record.
+ // I'm not sure if it should.
+ subcase = " EOCD good hidden by bad, ";
+ bytes = new byte[] {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x50, 0x4b, 0x05, 0x06, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ fileSystem.addFile(filename, bytes);
+ zipIn = newZipIn(filename);
+ try {
+ zipIn.endOfCentralDirectory();
+ fail(subcase + "expected IndexOutOfBoundsException");
+ } catch (Exception ex) {
+ assertSame(subcase + "caught exception", IndexOutOfBoundsException.class, ex.getClass());
+ }
+
+ // Minimal format checking here, assuming the EndOfDirectoryTest class
+ // test for that.
+
+ subcase = " EOCD truncated comment, ";
+ bytes = new byte[100];
+ buffer = ByteBuffer.wrap(bytes);
+ comment = "optional file comment";
+ commentLen = comment.getBytes(UTF_8).length;
+ offset = bytes.length - ZipInputStream.ENDHDR - commentLen;
+ buffer.position(offset);
+ EndOfCentralDirectory.view(buffer, comment);
+ byte[] truncated = Arrays.copyOf(bytes, bytes.length - 5);
+ fileSystem.addFile(filename, truncated);
+ zipIn = newZipIn(filename);
+ try { // not sure this is the exception we want!
+ zipIn.endOfCentralDirectory();
+ fail(subcase + "expected IllegalArgumentException");
+ } catch (Exception ex) {
+ assertSame(subcase + "caught exception", IllegalArgumentException.class, ex.getClass());
+ }
+
+ subcase = " EOCD no comment, ";
+ bytes = new byte[100];
+ buffer = ByteBuffer.wrap(bytes);
+ comment = null;
+ commentLen = 0;
+ offset = bytes.length - ZipInputStream.ENDHDR - commentLen;
+ buffer.position(offset);
+ EndOfCentralDirectory.view(buffer, comment);
+ fileSystem.addFile(filename, bytes);
+ zipIn = newZipIn(filename);
+ result = zipIn.endOfCentralDirectory();
+ assertNotNull(subcase + "found", result);
+ assertEquals(subcase + "comment", "", result.getComment());
+ assertEquals(subcase + "marker", ZipInputStream.ENDSIG, (int) result.get(ENDSIG));
+
+ subcase = " EOCD comment, ";
+ bytes = new byte[100];
+ buffer = ByteBuffer.wrap(bytes);
+ comment = "optional file comment";
+ commentLen = comment.getBytes(UTF_8).length;
+ offset = bytes.length - ZipInputStream.ENDHDR - commentLen;
+ buffer.position(offset);
+ EndOfCentralDirectory.view(buffer, comment);
+ assertEquals(subcase + "setup", comment,
+ new String(bytes, bytes.length - commentLen, commentLen, UTF_8));
+ fileSystem.addFile(filename, bytes);
+ zipIn = newZipIn(filename);
+ result = zipIn.endOfCentralDirectory();
+ assertNotNull(subcase + "found", result);
+ assertEquals(subcase + "comment", comment, result.getComment());
+ assertEquals(subcase + "marker", ZipInputStream.ENDSIG, (int) result.get(ENDSIG));
+
+ subcase = " EOCD extra data, ";
+ bytes = new byte[100];
+ buffer = ByteBuffer.wrap(bytes);
+ comment = null;
+ commentLen = 0;
+ offset = bytes.length - ZipInputStream.ENDHDR - commentLen - 10;
+ buffer.position(offset);
+ EndOfCentralDirectory.view(buffer, comment);
+ fileSystem.addFile(filename, bytes);
+ zipIn = newZipIn(filename);
+ result = zipIn.endOfCentralDirectory();
+ assertNotNull(subcase + "found", result);
+ assertEquals(subcase + "comment", "", result.getComment());
+ assertEquals(subcase + "marker", ZipInputStream.ENDSIG, (int) result.get(ENDSIG));
+ }
+
+ /**
+ * Test of centralDirectory method, of class ZipIn.
+ */
+ @Test
+ public void testCentralDirectory() throws Exception {
+ String filename = "test.zip";
+ ByteBuffer buffer;
+ int offset;
+ ZipIn zipIn;
+ String subcase;
+ subcase = " EOCD extra data, ";
+ String commonName = "thisIsNotNormal.txt";
+ int filenameLen = commonName.getBytes(UTF_8).length;
+ int count = ENTRY_COUNT;
+ int dirEntry = ZipInputStream.CENHDR;
+ int before = count;
+ int between = 0; // implementation doesn't tolerate data between dir entries, does the spec?
+ int after = 20;
+ int eocd = ZipInputStream.ENDHDR;
+ int total = before + (count * (dirEntry + filenameLen)) + ((count - 1) * between)
+ + after + eocd;
+ byte[] bytes = new byte[total];
+ offset = before;
+ for (int i = 0; i < count; i++) {
+ if (i > 0) {
+ offset += between;
+ }
+ buffer = ByteBuffer.wrap(bytes, offset, bytes.length - offset);
+ DirectoryEntry.view(buffer, commonName, null, null)
+ .set(CENHOW, (short) 8)
+ .set(CENSIZ, before)
+ .set(CENLEN, 2 * before)
+ .set(CENOFF, i); // Not valid of course, but we're only testing central dir parsing.
+ // and there are currently no checks in the parser to see if offset makes sense.
+ offset += dirEntry + filenameLen;
+ }
+ offset += after;
+ buffer = ByteBuffer.wrap(bytes, offset, bytes.length - offset);
+ EndOfCentralDirectory.view(buffer, null)
+ .set(ENDOFF, before)
+ .set(ENDSIZ, offset - before - after)
+ .set(ENDTOT, (short) count)
+ .set(ENDSUB, (short) count);
+
+ fileSystem.addFile(filename, bytes);
+ zipIn = newZipIn(filename);
+ CentralDirectory result = zipIn.centralDirectory();
+ assertNotNull(subcase + "found", result);
+ List<DirectoryEntry> list = result.list();
+ assertEquals(subcase + "size", count, list.size());
+ for (int i = 0; i < list.size(); i++) {
+ assertEquals(subcase + "offset check[" + i + "]", i, list.get(i).get(CENOFF));
+ }
+ }
+
+ /**
+ * Test of scanEntries method, of class ZipIn.
+ */
+ @Test
+ public void testScanEntries() throws Exception {
+ int count = ENTRY_COUNT * 100;
+ String filename = "test.jar";
+
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.scanEntries(new EntryHandler() {
+ int count = 0;
+ @Override
+ public void handle(ZipIn in, LocalFileHeader header, DirectoryEntry dirEntry,
+ ByteBuffer data) throws IOException {
+ assertSame(zipIn, in);
+ String filename = "pkg/f" + count + ".class";
+ assertEquals(filename, header.getFilename());
+ assertEquals(filename, dirEntry.getFilename());
+ count++;
+ }
+ });
+ }
+
+ /**
+ * Test of nextHeaderFrom method, of class ZipIn.
+ */
+ @Test
+ public void testNextHeaderFrom_long() throws Exception {
+ int count = ENTRY_COUNT;
+ String filename = "test.jar";
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.endOfCentralDirectory();
+ count = 0;
+ int offset = 0;
+ LocalFileHeader header;
+ do {
+ header = zipIn.nextHeaderFrom(offset);
+ String name = "pkg/f" + count + ".class";
+ if (header != null) {
+ assertEquals(name, header.getFilename());
+ count++;
+ offset = (int) header.fileOffset() + 4;
+ }
+ } while(header != null);
+ assertEquals(ENTRY_COUNT, count);
+ }
+
+ /**
+ * Test of nextHeaderFrom method, of class ZipIn.
+ */
+ @Test
+ public void testNextHeaderFrom_DirectoryEntry() throws Exception {
+ int count = ENTRY_COUNT;
+ String filename = "test.jar";
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.centralDirectory();
+ List<DirectoryEntry> list = zipIn.centralDirectory().list();
+ count = 0;
+ String name;
+ LocalFileHeader header = zipIn.nextHeaderFrom(null);
+ for (DirectoryEntry dirEntry : list) {
+ name = "pkg/f" + count + ".class";
+ assertEquals(name, dirEntry.getFilename());
+ assertEquals(name, header.getFilename());
+ header = zipIn.nextHeaderFrom(dirEntry);
+ count++;
+ }
+ assertNull(header);
+ }
+
+ /**
+ * Test of localHeaderFor method, of class ZipIn.
+ */
+ @Test
+ public void testLocalHeaderFor() throws Exception {
+ int count = ENTRY_COUNT;
+ String filename = "test.jar";
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.centralDirectory();
+ List<DirectoryEntry> list = zipIn.centralDirectory().list();
+ count = 0;
+ String name;
+ LocalFileHeader header;
+ for (DirectoryEntry dirEntry : list) {
+ name = "pkg/f" + count + ".class";
+ header = zipIn.localHeaderFor(dirEntry);
+ assertEquals(name, dirEntry.getFilename());
+ assertEquals(name, header.getFilename());
+ count++;
+ }
+ }
+
+ /**
+ * Test of localHeaderAt method, of class ZipIn.
+ */
+ @Test
+ public void testLocalHeaderAt() throws Exception {
+ int count = ENTRY_COUNT;
+ String filename = "test.jar";
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.centralDirectory();
+ List<DirectoryEntry> list = zipIn.centralDirectory().list();
+ count = 0;
+ String name;
+ LocalFileHeader header;
+ for (DirectoryEntry dirEntry : list) {
+ name = "pkg/f" + count + ".class";
+ header = zipIn.localHeaderAt(dirEntry.get(CENOFF));
+ assertEquals(name, dirEntry.getFilename());
+ assertEquals(name, header.getFilename());
+ count++;
+ }
+ }
+
+ /**
+ * Test of nextFrom method, of class ZipIn.
+ */
+ @Test
+ public void testNextFrom_long() throws Exception {
+ int count = ENTRY_COUNT;
+ String filename = "test.jar";
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.centralDirectory();
+ count = 0;
+ int offset = 0;
+ ZipEntry zipEntry;
+ do {
+ zipEntry = zipIn.nextFrom(offset);
+ String name = "pkg/f" + count + ".class";
+ if (zipEntry.getCode() != ZipEntry.Status.ENTRY_NOT_FOUND) {
+ assertNotNull(zipEntry.getHeader());
+ assertNotNull(zipEntry.getDirEntry());
+ assertEquals(name, zipEntry.getHeader().getFilename());
+ assertEquals(name, zipEntry.getDirEntry().getFilename());
+ count++;
+ offset = (int) zipEntry.getHeader().fileOffset() + 4;
+ }
+ } while(zipEntry.getCode() != ZipEntry.Status.ENTRY_NOT_FOUND);
+ assertEquals(ENTRY_COUNT, count);
+ }
+
+ /**
+ * Test of nextFrom method, of class ZipIn.
+ */
+ @Test
+ public void testNextFrom_DirectoryEntry() throws Exception {
+ int count = ENTRY_COUNT;
+ String filename = "test.jar";
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.centralDirectory();
+ List<DirectoryEntry> list = zipIn.centralDirectory().list();
+ count = 0;
+ String name;
+ ZipEntry zipEntry = zipIn.nextFrom(null);
+ for (DirectoryEntry dirEntry : list) {
+ if (zipEntry.getCode() == ZipEntry.Status.ENTRY_NOT_FOUND) {
+ break;
+ }
+ name = "pkg/f" + count + ".class";
+ assertNotNull(zipEntry.getHeader());
+ assertNotNull(zipEntry.getDirEntry());
+ assertEquals(name, zipEntry.getHeader().getFilename());
+ assertEquals(name, zipEntry.getDirEntry().getFilename());
+ zipEntry = zipIn.nextFrom(dirEntry);
+ count++;
+ }
+ assertEquals(ENTRY_COUNT, count);
+ }
+
+ /**
+ * Test of entryAt method, of class ZipIn.
+ */
+ @Test
+ public void testEntryAt() throws Exception {
+ int count = ENTRY_COUNT;
+ String filename = "test.jar";
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.centralDirectory();
+ List<DirectoryEntry> list = zipIn.centralDirectory().list();
+ count = 0;
+ String name;
+ ZipEntry zipEntry;
+ for (DirectoryEntry dirEntry : list) {
+ zipEntry = zipIn.entryAt(dirEntry.get(CENOFF));
+ name = "pkg/f" + count + ".class";
+ assertNotNull(zipEntry.getHeader());
+ assertNotNull(zipEntry.getDirEntry());
+ assertEquals(name, zipEntry.getHeader().getFilename());
+ assertEquals(name, zipEntry.getDirEntry().getFilename());
+ count++;
+ }
+ assertEquals(ENTRY_COUNT, count);
+ }
+
+ /**
+ * Test of entryWith method, of class ZipIn.
+ */
+ @Test
+ public void testEntryWith() throws Exception {
+ int count = ENTRY_COUNT;
+ String filename = "test.jar";
+ ZipFileBuilder builder = new ZipFileBuilder();
+ for (int i = 0; i < count; i++) {
+ builder.add("pkg/f" + i + ".class", "All day long");
+ }
+ builder.create(filename);
+ final ZipIn zipIn = newZipIn(filename);
+ zipIn.centralDirectory();
+ count = 0;
+ int offset = 0;
+ LocalFileHeader header;
+ do {
+ header = zipIn.nextHeaderFrom(offset);
+ String name = "pkg/f" + count + ".class";
+ if (header != null) {
+ ZipEntry zipEntry = zipIn.entryWith(header);
+ assertNotNull(zipEntry.getDirEntry());
+ assertSame(header, zipEntry.getHeader());
+ assertEquals(name, zipEntry.getHeader().getFilename());
+ assertEquals(name, zipEntry.getDirEntry().getFilename());
+ assertEquals(name, header.getFilename());
+ count++;
+ offset = (int) header.fileOffset() + 4;
+ }
+ } while(header != null);
+ assertEquals(ENTRY_COUNT, count);
+ }
+
+ private ZipIn newZipIn(String filename) throws IOException {
+ return new ZipIn(fileSystem.getInputChannel(filename), filename);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/android/ziputils/ZipOutTest.java b/src/test/java/com/google/devtools/build/android/ziputils/ZipOutTest.java
new file mode 100644
index 0000000000..1b9cf39979
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/ziputils/ZipOutTest.java
@@ -0,0 +1,51 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.android.ziputils;
+
+import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENTIM;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Unit tests for {@link ZipOut}.
+ */
+@RunWith(JUnit4.class)
+public class ZipOutTest {
+
+ private static final FakeFileSystem fileSystem = new FakeFileSystem();
+
+ @Test
+ public void testNextEntry() {
+ try {
+ String filename = "out.zip";
+ ZipOut instance = new ZipOut(fileSystem.getOutputChannel(filename, false), filename);
+
+ instance.nextEntry(DirectoryEntry.allocate("pgk/a.class", null, null))
+ .set(CENTIM, DosTime.EPOCH.time);
+
+ instance.nextEntry(DirectoryEntry.allocate("pgk/b.class", null, null))
+ .set(CENTIM, DosTime.EPOCH.time);
+
+ instance.close();
+ } catch (IOException ex) {
+ Logger.getLogger(ZipOutTest.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ }
+}