jdk/test/java/util/zip/ZipFile/TestZipFile.java
author sherman
Tue, 06 Dec 2016 14:44:12 -0800
changeset 42434 7fc88840baa5
parent 40684 2e37c119dc2a
permissions -rw-r--r--
8170828: test/java/util/zip/ZipFile/TestZipFile needs @modules to work with Method.setAccessible() Reviewed-by: martin, rriggs

/*
 * 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");}
}