| /* |
| * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test |
| * @bug 8142508 8146431 |
| * @modules java.base/java.util.zip:open |
| * @summary Tests various ZipFile apis |
| * @run main/manual TestZipFile |
| */ |
| |
| import java.io.*; |
| import java.lang.reflect.Method; |
| import java.nio.*; |
| import java.nio.file.*; |
| import java.nio.file.attribute.*; |
| import java.util.*; |
| import java.util.concurrent.*; |
| import java.util.zip.*; |
| |
| public class TestZipFile { |
| |
| private static Random r = new Random(); |
| private static int N = 50; |
| private static int NN = 10; |
| private static int ENUM = 10000; |
| private static int ESZ = 10000; |
| private static ExecutorService executor = Executors.newFixedThreadPool(20); |
| private static Set<Path> paths = new HashSet<>(); |
| |
| static void realMain (String[] args) throws Throwable { |
| |
| try { |
| for (int i = 0; i < N; i++) { |
| test(r.nextInt(ENUM), r.nextInt(ESZ), false, true); |
| test(r.nextInt(ENUM), r.nextInt(ESZ), true, true); |
| } |
| |
| for (int i = 0; i < NN; i++) { |
| test(r.nextInt(ENUM), 100000 + r.nextInt(ESZ), false, true); |
| test(r.nextInt(ENUM), 100000 + r.nextInt(ESZ), true, true); |
| testCachedDelete(); |
| testCachedOverwrite(); |
| //test(r.nextInt(ENUM), r.nextInt(ESZ), false, true); |
| } |
| |
| test(70000, 1000, false, true); // > 65536 entry number; |
| testDelete(); // OPEN_DELETE |
| |
| executor.shutdown(); |
| executor.awaitTermination(10, TimeUnit.MINUTES); |
| } finally { |
| for (Path path : paths) { |
| Files.deleteIfExists(path); |
| } |
| } |
| } |
| |
| static void test(int numEntry, int szMax, boolean addPrefix, boolean cleanOld) { |
| String name = "zftest" + r.nextInt() + ".zip"; |
| Zip zip = new Zip(name, numEntry, szMax, addPrefix, cleanOld); |
| for (int i = 0; i < NN; i++) { |
| executor.submit(() -> doTest(zip)); |
| } |
| } |
| |
| // test scenario: |
| // (1) open the ZipFile(zip) with OPEN_READ | OPEN_DELETE |
| // (2) test the ZipFile works correctly |
| // (3) check the zip is deleted after ZipFile gets closed |
| static void testDelete() throws Throwable { |
| String name = "zftest" + r.nextInt() + ".zip"; |
| Zip zip = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true); |
| try (ZipFile zf = new ZipFile(new File(zip.name), |
| ZipFile.OPEN_READ | ZipFile.OPEN_DELETE )) |
| { |
| doTest0(zip, zf); |
| } |
| Path p = Paths.get(name); |
| if (Files.exists(p)) { |
| fail("Failed to delete " + name + " with OPEN_DELETE"); |
| } |
| } |
| |
| // test scenario: |
| // (1) keep a ZipFile(zip1) alive (in ZipFile's cache), dont close it |
| // (2) delete zip1 and create zip2 with the same name the zip1 with zip2 |
| // (3) zip1 tests should fail, but no crash |
| // (4) zip2 tasks should all get zip2, then pass normal testing. |
| static void testCachedDelete() throws Throwable { |
| String name = "zftest" + r.nextInt() + ".zip"; |
| Zip zip1 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true); |
| |
| try (ZipFile zf = new ZipFile(zip1.name)) { |
| for (int i = 0; i < NN; i++) { |
| executor.submit(() -> verifyNoCrash(zip1)); |
| } |
| // delete the "zip1" and create a new one to test |
| Zip zip2 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true); |
| /* |
| System.out.println("========================================"); |
| System.out.printf(" zip1=%s, mt=%d, enum=%d%n ->attrs=[key=%s, sz=%d, mt=%d]%n", |
| zip1.name, zip1.lastModified, zip1.entries.size(), |
| zip1.attrs.fileKey(), zip1.attrs.size(), zip1.attrs.lastModifiedTime().toMillis()); |
| System.out.printf(" zip2=%s, mt=%d, enum=%d%n ->attrs=[key=%s, sz=%d, mt=%d]%n", |
| zip2.name, zip2.lastModified, zip2.entries.size(), |
| zip2.attrs.fileKey(), zip2.attrs.size(), zip2.attrs.lastModifiedTime().toMillis()); |
| */ |
| for (int i = 0; i < NN; i++) { |
| executor.submit(() -> doTest(zip2)); |
| } |
| } |
| } |
| |
| // overwrite the "zip1" and create a new one to test. So the two zip files |
| // have the same fileKey, but probably different lastModified() |
| static void testCachedOverwrite() throws Throwable { |
| String name = "zftest" + r.nextInt() + ".zip"; |
| Zip zip1 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true); |
| try (ZipFile zf = new ZipFile(zip1.name)) { |
| for (int i = 0; i < NN; i++) { |
| executor.submit(() -> verifyNoCrash(zip1)); |
| } |
| // overwrite the "zip1" with new contents |
| Zip zip2 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, false); |
| for (int i = 0; i < NN; i++) { |
| executor.submit(() -> doTest(zip2)); |
| } |
| } |
| } |
| |
| // just check the entries and contents. since the file has been either overwritten |
| // or deleted/rewritten, we only care if it crahes or not. |
| static void verifyNoCrash(Zip zip) throws RuntimeException { |
| try (ZipFile zf = new ZipFile(zip.name)) { |
| List<ZipEntry> zlist = new ArrayList(zip.entries.keySet()); |
| String[] elist = zf.stream().map( e -> e.getName()).toArray(String[]::new); |
| if (!Arrays.equals(elist, |
| zlist.stream().map( e -> e.getName()).toArray(String[]::new))) |
| { |
| //System.out.printf("++++++ LIST NG [%s] entries.len=%d, expected=%d+++++++%n", |
| // zf.getName(), elist.length, zlist.size()); |
| return; |
| } |
| for (ZipEntry ze : zlist) { |
| byte[] zdata = zip.entries.get(ze); |
| ZipEntry e = zf.getEntry(ze.getName()); |
| if (e != null) { |
| checkEqual(e, ze); |
| if (!e.isDirectory()) { |
| // check with readAllBytes |
| try (InputStream is = zf.getInputStream(e)) { |
| if (!Arrays.equals(zdata, is.readAllBytes())) { |
| //System.out.printf("++++++ BYTES NG [%s]/[%s] ++++++++%n", |
| // zf.getName(), ze.getName()); |
| } |
| } |
| } |
| } |
| } |
| } catch (Throwable t) { |
| // t.printStackTrace(); |
| // fail(t.toString()); |
| } |
| } |
| |
| static void checkEqual(ZipEntry x, ZipEntry y) { |
| if (x.getName().equals(y.getName()) && |
| x.isDirectory() == y.isDirectory() && |
| x.getMethod() == y.getMethod() && |
| (x.getTime() / 2000) == y.getTime() / 2000 && |
| x.getSize() == y.getSize() && |
| x.getCompressedSize() == y.getCompressedSize() && |
| x.getCrc() == y.getCrc() && |
| x.getComment().equals(y.getComment()) |
| ) { |
| pass(); |
| } else { |
| fail(x + " not equal to " + y); |
| System.out.printf(" %s %s%n", x.getName(), y.getName()); |
| System.out.printf(" %d %d%n", x.getMethod(), y.getMethod()); |
| System.out.printf(" %d %d%n", x.getTime(), y.getTime()); |
| System.out.printf(" %d %d%n", x.getSize(), y.getSize()); |
| System.out.printf(" %d %d%n", x.getCompressedSize(), y.getCompressedSize()); |
| System.out.printf(" %d %d%n", x.getCrc(), y.getCrc()); |
| System.out.println("-----------------"); |
| } |
| } |
| |
| static void doTest(Zip zip) throws RuntimeException { |
| //Thread me = Thread.currentThread(); |
| try (ZipFile zf = new ZipFile(zip.name)) { |
| doTest0(zip, zf); |
| } catch (Throwable t) { |
| throw new RuntimeException(t); |
| } |
| } |
| |
| static void doTest0(Zip zip, ZipFile zf) throws Throwable { |
| // (0) check zero-length entry name, no AIOOBE |
| try { |
| check(zf.getEntry("") == null);; |
| } catch (Throwable t) { |
| unexpected(t); |
| } |
| |
| List<ZipEntry> list = new ArrayList(zip.entries.keySet()); |
| // (1) check entry list, in expected order |
| if (!check(Arrays.equals( |
| list.stream().map( e -> e.getName()).toArray(String[]::new), |
| zf.stream().map( e -> e.getName()).toArray(String[]::new)))) { |
| return; |
| } |
| // (2) shuffle, and check each entry and its bytes |
| Collections.shuffle(list); |
| for (ZipEntry ze : list) { |
| byte[] data = zip.entries.get(ze); |
| ZipEntry e = zf.getEntry(ze.getName()); |
| checkEqual(e, ze); |
| if (!e.isDirectory()) { |
| // check with readAllBytes |
| try (InputStream is = zf.getInputStream(e)) { |
| check(Arrays.equals(data, is.readAllBytes())); |
| } |
| // check with smaller sized buf |
| try (InputStream is = zf.getInputStream(e)) { |
| byte[] buf = new byte[(int)e.getSize()]; |
| int sz = r.nextInt((int)e.getSize()/4 + 1) + 1; |
| int off = 0; |
| int n; |
| while ((n = is.read(buf, off, buf.length - off)) > 0) { |
| off += n; |
| } |
| check(is.read() == -1); |
| check(Arrays.equals(data, buf)); |
| } |
| } |
| } |
| // (3) check getMetaInfEntryNames |
| String[] metas = list.stream() |
| .map( e -> e.getName()) |
| .filter( s -> s.startsWith("META-INF/")) |
| .sorted() |
| .toArray(String[]::new); |
| if (metas.length > 0) { |
| // meta-inf entries |
| Method getMetas = ZipFile.class.getDeclaredMethod("getMetaInfEntryNames"); |
| getMetas.setAccessible(true); |
| String[] names = (String[])getMetas.invoke(zf); |
| if (names == null) { |
| fail("Failed to get metanames from " + zf); |
| } else { |
| Arrays.sort(names); |
| check(Arrays.equals(names, metas)); |
| } |
| } |
| } |
| |
| private static class Zip { |
| String name; |
| Map<ZipEntry, byte[]> entries; |
| BasicFileAttributes attrs; |
| long lastModified; |
| |
| Zip(String name, int num, int szMax, boolean prefix, boolean clean) { |
| this.name = name; |
| entries = new LinkedHashMap<>(num); |
| try { |
| Path p = Paths.get(name); |
| if (clean) { |
| Files.deleteIfExists(p); |
| } |
| paths.add(p); |
| } catch (Exception x) { |
| throw (RuntimeException)x; |
| } |
| |
| try (FileOutputStream fos = new FileOutputStream(name); |
| BufferedOutputStream bos = new BufferedOutputStream(fos); |
| ZipOutputStream zos = new ZipOutputStream(bos)) |
| { |
| if (prefix) { |
| byte[] bytes = new byte[r.nextInt(1000)]; |
| r.nextBytes(bytes); |
| bos.write(bytes); |
| } |
| CRC32 crc = new CRC32(); |
| for (int i = 0; i < num; i++) { |
| String ename = "entry-" + i + "-name-" + r.nextLong(); |
| ZipEntry ze = new ZipEntry(ename); |
| int method = r.nextBoolean() ? ZipEntry.STORED : ZipEntry.DEFLATED; |
| writeEntry(zos, crc, ze, ZipEntry.STORED, szMax); |
| } |
| // add some manifest entries |
| for (int i = 0; i < r.nextInt(20); i++) { |
| String meta = "META-INF/" + "entry-" + i + "-metainf-" + r.nextLong(); |
| ZipEntry ze = new ZipEntry(meta); |
| writeEntry(zos, crc, ze, ZipEntry.STORED, szMax); |
| } |
| } catch (Exception x) { |
| throw (RuntimeException)x; |
| } |
| try { |
| this.attrs = Files.readAttributes(Paths.get(name), BasicFileAttributes.class); |
| this.lastModified = new File(name).lastModified(); |
| } catch (Exception x) { |
| throw (RuntimeException)x; |
| } |
| } |
| |
| private void writeEntry(ZipOutputStream zos, CRC32 crc, |
| ZipEntry ze, int method, int szMax) |
| throws IOException |
| { |
| ze.setMethod(method); |
| byte[] data = new byte[r.nextInt(szMax + 1)]; |
| r.nextBytes(data); |
| if (method == ZipEntry.STORED) { // must set size/csize/crc |
| ze.setSize(data.length); |
| ze.setCompressedSize(data.length); |
| crc.reset(); |
| crc.update(data); |
| ze.setCrc(crc.getValue()); |
| } |
| ze.setTime(System.currentTimeMillis()); |
| ze.setComment(ze.getName()); |
| zos.putNextEntry(ze); |
| zos.write(data); |
| zos.closeEntry(); |
| entries.put(ze, data); |
| } |
| } |
| |
| //--------------------- Infrastructure --------------------------- |
| static volatile int passed = 0, failed = 0; |
| static void pass() {passed++;} |
| static void pass(String msg) {System.out.println(msg); passed++;} |
| static void fail() {failed++; Thread.dumpStack();} |
| static void fail(String msg) {System.out.println(msg); fail();} |
| static void unexpected(Throwable t) {failed++; t.printStackTrace();} |
| static void unexpected(Throwable t, String msg) { |
| System.out.println(msg); failed++; t.printStackTrace();} |
| static boolean check(boolean cond) {if (cond) pass(); else fail(); return cond;} |
| |
| public static void main(String[] args) throws Throwable { |
| try {realMain(args);} catch (Throwable t) {unexpected(t);} |
| System.out.println("\nPassed = " + passed + " failed = " + failed); |
| if (failed > 0) throw new AssertionError("Some tests failed");} |
| } |