TestZipFile.java revision 15491:6f390eafc676
1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 8142508 8146431
27 * @summary Tests various ZipFile apis
28 * @run main/manual TestZipFile
29 */
30
31import java.io.*;
32import java.lang.reflect.Method;
33import java.nio.*;
34import java.nio.file.*;
35import java.nio.file.attribute.*;
36import java.util.*;
37import java.util.concurrent.*;
38import java.util.zip.*;
39
40public class TestZipFile {
41
42    private static Random r = new Random();
43    private static int    N = 50;
44    private static int    NN = 10;
45    private static int    ENUM = 10000;
46    private static int    ESZ = 10000;
47    private static ExecutorService executor = Executors.newFixedThreadPool(20);
48    private static Set<Path> paths = new HashSet<>();
49
50    static void realMain (String[] args) throws Throwable {
51
52        try {
53            for (int i = 0; i < N; i++) {
54                test(r.nextInt(ENUM), r.nextInt(ESZ), false, true);
55                test(r.nextInt(ENUM), r.nextInt(ESZ), true, true);
56            }
57
58            for (int i = 0; i < NN; i++) {
59                test(r.nextInt(ENUM), 100000 + r.nextInt(ESZ), false, true);
60                test(r.nextInt(ENUM), 100000 + r.nextInt(ESZ), true, true);
61                testCachedDelete();
62                testCachedOverwrite();
63                //test(r.nextInt(ENUM), r.nextInt(ESZ), false, true);
64            }
65
66            test(70000, 1000, false, true);   // > 65536 entry number;
67            testDelete();                     // OPEN_DELETE
68
69            executor.shutdown();
70            executor.awaitTermination(10, TimeUnit.MINUTES);
71        } finally {
72            for (Path path : paths) {
73                Files.deleteIfExists(path);
74            }
75        }
76    }
77
78    static void test(int numEntry, int szMax, boolean addPrefix, boolean cleanOld) {
79        String name = "zftest" + r.nextInt() + ".zip";
80        Zip zip = new Zip(name, numEntry, szMax, addPrefix, cleanOld);
81        for (int i = 0; i < NN; i++) {
82            executor.submit(() -> doTest(zip));
83        }
84     }
85
86    // test scenario:
87    // (1) open the ZipFile(zip) with OPEN_READ | OPEN_DELETE
88    // (2) test the ZipFile works correctly
89    // (3) check the zip is deleted after ZipFile gets closed
90    static void testDelete() throws Throwable {
91        String name = "zftest" + r.nextInt() + ".zip";
92        Zip zip = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true);
93        try (ZipFile zf = new ZipFile(new File(zip.name),
94                                      ZipFile.OPEN_READ | ZipFile.OPEN_DELETE ))
95        {
96            doTest0(zip, zf);
97        }
98        Path p = Paths.get(name);
99        if (Files.exists(p)) {
100            fail("Failed to delete " + name + " with OPEN_DELETE");
101        }
102    }
103
104    // test scenario:
105    // (1) keep a ZipFile(zip1) alive (in ZipFile's cache), dont close it
106    // (2) delete zip1 and create zip2 with the same name the zip1 with zip2
107    // (3) zip1 tests should fail, but no crash
108    // (4) zip2 tasks should all get zip2, then pass normal testing.
109    static void testCachedDelete() throws Throwable {
110        String name = "zftest" + r.nextInt() + ".zip";
111        Zip zip1 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true);
112
113        try (ZipFile zf = new ZipFile(zip1.name)) {
114            for (int i = 0; i < NN; i++) {
115                executor.submit(() -> verifyNoCrash(zip1));
116            }
117            // delete the "zip1"  and create a new one to test
118            Zip zip2 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true);
119            /*
120                System.out.println("========================================");
121                System.out.printf("    zip1=%s, mt=%d, enum=%d%n    ->attrs=[key=%s, sz=%d, mt=%d]%n",
122                    zip1.name, zip1.lastModified, zip1.entries.size(),
123                    zip1.attrs.fileKey(), zip1.attrs.size(), zip1.attrs.lastModifiedTime().toMillis());
124                System.out.printf("    zip2=%s, mt=%d, enum=%d%n    ->attrs=[key=%s, sz=%d, mt=%d]%n",
125                    zip2.name, zip2.lastModified, zip2.entries.size(),
126                    zip2.attrs.fileKey(), zip2.attrs.size(), zip2.attrs.lastModifiedTime().toMillis());
127            */
128            for (int i = 0; i < NN; i++) {
129                executor.submit(() -> doTest(zip2));
130            }
131        }
132    }
133
134   // overwrite the "zip1"  and create a new one to test. So the two zip files
135   // have the same fileKey, but probably different lastModified()
136    static void testCachedOverwrite() throws Throwable {
137        String name = "zftest" + r.nextInt() + ".zip";
138        Zip zip1 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, true);
139        try (ZipFile zf = new ZipFile(zip1.name)) {
140            for (int i = 0; i < NN; i++) {
141                executor.submit(() -> verifyNoCrash(zip1));
142            }
143            // overwrite the "zip1"  with new contents
144            Zip zip2 = new Zip(name, r.nextInt(ENUM), r.nextInt(ESZ), false, false);
145            for (int i = 0; i < NN; i++) {
146                executor.submit(() -> doTest(zip2));
147            }
148        }
149    }
150
151    // just check the entries and contents. since the file has been either overwritten
152    // or deleted/rewritten, we only care if it crahes or not.
153    static void verifyNoCrash(Zip zip) throws RuntimeException {
154        try (ZipFile zf = new ZipFile(zip.name)) {
155            List<ZipEntry> zlist = new ArrayList(zip.entries.keySet());
156            String[] elist = zf.stream().map( e -> e.getName()).toArray(String[]::new);
157            if (!Arrays.equals(elist,
158                               zlist.stream().map( e -> e.getName()).toArray(String[]::new)))
159            {
160                //System.out.printf("++++++ LIST NG [%s] entries.len=%d, expected=%d+++++++%n",
161                //                  zf.getName(), elist.length, zlist.size());
162                return;
163            }
164            for (ZipEntry ze : zlist) {
165                byte[] zdata = zip.entries.get(ze);
166                ZipEntry e = zf.getEntry(ze.getName());
167                if (e != null) {
168                    checkEqual(e, ze);
169                    if (!e.isDirectory()) {
170                        // check with readAllBytes
171                        try (InputStream is = zf.getInputStream(e)) {
172                            if (!Arrays.equals(zdata, is.readAllBytes())) {
173                                //System.out.printf("++++++ BYTES NG  [%s]/[%s] ++++++++%n",
174                                //                  zf.getName(), ze.getName());
175                            }
176                        }
177                    }
178                }
179            }
180        } catch (Throwable t) {
181            // t.printStackTrace();
182            // fail(t.toString());
183        }
184    }
185
186    static void checkEqual(ZipEntry x, ZipEntry y) {
187        if (x.getName().equals(y.getName()) &&
188            x.isDirectory() == y.isDirectory() &&
189            x.getMethod() == y.getMethod() &&
190            (x.getTime() / 2000) == y.getTime() / 2000 &&
191            x.getSize() == y.getSize() &&
192            x.getCompressedSize() == y.getCompressedSize() &&
193            x.getCrc() == y.getCrc() &&
194            x.getComment().equals(y.getComment())
195        ) {
196            pass();
197        } else {
198            fail(x + " not equal to " + y);
199            System.out.printf("      %s       %s%n", x.getName(), y.getName());
200            System.out.printf("      %d       %d%n", x.getMethod(), y.getMethod());
201            System.out.printf("      %d       %d%n", x.getTime(), y.getTime());
202            System.out.printf("      %d       %d%n", x.getSize(), y.getSize());
203            System.out.printf("      %d       %d%n", x.getCompressedSize(), y.getCompressedSize());
204            System.out.printf("      %d       %d%n", x.getCrc(), y.getCrc());
205            System.out.println("-----------------");
206        }
207    }
208
209    static void doTest(Zip zip) throws RuntimeException {
210        //Thread me = Thread.currentThread();
211        try (ZipFile zf = new ZipFile(zip.name)) {
212            doTest0(zip, zf);
213        } catch (Throwable t) {
214            throw new RuntimeException(t);
215        }
216    }
217
218    static void doTest0(Zip zip, ZipFile zf) throws Throwable {
219        // (0) check zero-length entry name, no AIOOBE
220        try {
221            check(zf.getEntry("") == null);;
222        } catch (Throwable t) {
223            unexpected(t);
224        }
225
226        List<ZipEntry> list = new ArrayList(zip.entries.keySet());
227        // (1) check entry list, in expected order
228        if (!check(Arrays.equals(
229                list.stream().map( e -> e.getName()).toArray(String[]::new),
230                zf.stream().map( e -> e.getName()).toArray(String[]::new)))) {
231            return;
232        }
233        // (2) shuffle, and check each entry and its bytes
234        Collections.shuffle(list);
235        for (ZipEntry ze : list) {
236            byte[] data = zip.entries.get(ze);
237            ZipEntry e = zf.getEntry(ze.getName());
238            checkEqual(e, ze);
239            if (!e.isDirectory()) {
240                // check with readAllBytes
241                try (InputStream is = zf.getInputStream(e)) {
242                    check(Arrays.equals(data, is.readAllBytes()));
243                }
244                // check with smaller sized buf
245                try (InputStream is = zf.getInputStream(e)) {
246                    byte[] buf = new byte[(int)e.getSize()];
247                    int sz = r.nextInt((int)e.getSize()/4 + 1) + 1;
248                    int off = 0;
249                    int n;
250                    while ((n = is.read(buf, off, buf.length - off)) > 0) {
251                        off += n;
252                    }
253                    check(is.read() == -1);
254                    check(Arrays.equals(data, buf));
255                }
256            }
257        }
258        // (3) check getMetaInfEntryNames
259        String[] metas = list.stream()
260                             .map( e -> e.getName())
261                             .filter( s -> s.startsWith("META-INF/"))
262                             .sorted()
263                             .toArray(String[]::new);
264        if (metas.length > 0) {
265            // meta-inf entries
266            Method getMetas = ZipFile.class.getDeclaredMethod("getMetaInfEntryNames");
267            getMetas.setAccessible(true);
268            String[] names = (String[])getMetas.invoke(zf);
269            if (names == null) {
270                fail("Failed to get metanames from " + zf);
271            } else {
272                Arrays.sort(names);
273                check(Arrays.equals(names, metas));
274            }
275        }
276    }
277
278    private static class Zip {
279        String name;
280        Map<ZipEntry, byte[]> entries;
281        BasicFileAttributes attrs;
282        long lastModified;
283
284        Zip(String name, int num, int szMax, boolean prefix, boolean clean) {
285            this.name = name;
286            entries = new LinkedHashMap<>(num);
287            try {
288                Path p = Paths.get(name);
289                if (clean) {
290                    Files.deleteIfExists(p);
291                }
292                paths.add(p);
293            } catch (Exception x) {
294                throw (RuntimeException)x;
295            }
296
297            try (FileOutputStream fos = new FileOutputStream(name);
298                 BufferedOutputStream bos = new BufferedOutputStream(fos);
299                 ZipOutputStream zos = new ZipOutputStream(bos))
300            {
301                if (prefix) {
302                    byte[] bytes = new byte[r.nextInt(1000)];
303                    r.nextBytes(bytes);
304                    bos.write(bytes);
305                }
306                CRC32 crc = new CRC32();
307                for (int i = 0; i < num; i++) {
308                    String ename = "entry-" + i + "-name-" + r.nextLong();
309                    ZipEntry ze = new ZipEntry(ename);
310                    int method = r.nextBoolean() ? ZipEntry.STORED : ZipEntry.DEFLATED;
311                    writeEntry(zos, crc, ze, ZipEntry.STORED, szMax);
312                }
313                // add some manifest entries
314                for (int i = 0; i < r.nextInt(20); i++) {
315                    String meta = "META-INF/" + "entry-" + i + "-metainf-" + r.nextLong();
316                    ZipEntry ze = new ZipEntry(meta);
317                    writeEntry(zos, crc, ze, ZipEntry.STORED, szMax);
318                }
319            } catch (Exception x) {
320                throw (RuntimeException)x;
321            }
322            try {
323                this.attrs = Files.readAttributes(Paths.get(name), BasicFileAttributes.class);
324                this.lastModified = new File(name).lastModified();
325            } catch (Exception x) {
326                throw (RuntimeException)x;
327            }
328        }
329
330        private void writeEntry(ZipOutputStream zos, CRC32 crc,
331                                ZipEntry ze, int method, int szMax)
332            throws IOException
333        {
334            ze.setMethod(method);
335            byte[] data = new byte[r.nextInt(szMax + 1)];
336            r.nextBytes(data);
337            if (method == ZipEntry.STORED) {  // must set size/csize/crc
338                ze.setSize(data.length);
339                ze.setCompressedSize(data.length);
340                crc.reset();
341                crc.update(data);
342                ze.setCrc(crc.getValue());
343            }
344            ze.setTime(System.currentTimeMillis());
345            ze.setComment(ze.getName());
346            zos.putNextEntry(ze);
347            zos.write(data);
348            zos.closeEntry();
349            entries.put(ze, data);
350        }
351    }
352
353    //--------------------- Infrastructure ---------------------------
354    static volatile int passed = 0, failed = 0;
355    static void pass() {passed++;}
356    static void pass(String msg) {System.out.println(msg); passed++;}
357    static void fail() {failed++; Thread.dumpStack();}
358    static void fail(String msg) {System.out.println(msg); fail();}
359    static void unexpected(Throwable t) {failed++; t.printStackTrace();}
360    static void unexpected(Throwable t, String msg) {
361        System.out.println(msg); failed++; t.printStackTrace();}
362    static boolean check(boolean cond) {if (cond) pass(); else fail(); return cond;}
363
364    public static void main(String[] args) throws Throwable {
365        try {realMain(args);} catch (Throwable t) {unexpected(t);}
366        System.out.println("\nPassed = " + passed + " failed = " + failed);
367        if (failed > 0) throw new AssertionError("Some tests failed");}
368}
369