View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.pack200;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.BufferedReader;
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileNotFoundException;
30  import java.io.FileOutputStream;
31  import java.io.FileReader;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.io.InputStreamReader;
35  import java.net.URISyntaxException;
36  import java.util.Enumeration;
37  import java.util.jar.JarEntry;
38  import java.util.jar.JarFile;
39  import java.util.jar.JarOutputStream;
40  
41  import org.apache.commons.compress.AbstractTempDirTest;
42  import org.junit.jupiter.api.Test;
43  
44  /**
45   * Test different options for packing a Jar file.
46   */
47  public class PackingOptionsTest extends AbstractTempDirTest {
48  
49      private void compareFiles(final JarFile jarFile, final JarFile jarFile2) throws IOException {
50          final Enumeration<JarEntry> entries = jarFile.entries();
51          while (entries.hasMoreElements()) {
52  
53              final JarEntry entry = entries.nextElement();
54              assertNotNull(entry);
55  
56              final String name = entry.getName();
57              final JarEntry entry2 = jarFile2.getJarEntry(name);
58              assertNotNull(entry2, "Missing Entry: " + name);
59              // assertEquals(entry.getTime(), entry2.getTime());
60              if (!name.equals("META-INF/MANIFEST.MF")) {
61                  // Manifests aren't necessarily byte-for-byte identical
62                  final InputStream ours = jarFile.getInputStream(entry);
63                  final InputStream expected = jarFile2.getInputStream(entry2);
64  
65                  try (BufferedReader reader1 = new BufferedReader(new InputStreamReader(ours));
66                          BufferedReader reader2 = new BufferedReader(new InputStreamReader(expected))) {
67                      String line1 = reader1.readLine();
68                      String line2 = reader2.readLine();
69                      int i = 1;
70                      while (line1 != null || line2 != null) {
71                          assertEquals(line2, line1, "Unpacked files differ for " + name + " at line " + i);
72                          line1 = reader1.readLine();
73                          line2 = reader2.readLine();
74                          i++;
75                      }
76                  }
77              }
78          }
79      }
80  
81      private void compareJarEntries(final JarFile jarFile, final JarFile jarFile2) {
82          final Enumeration<JarEntry> entries = jarFile.entries();
83          while (entries.hasMoreElements()) {
84  
85              final JarEntry entry = entries.nextElement();
86              assertNotNull(entry);
87  
88              final String name = entry.getName();
89              final JarEntry entry2 = jarFile2.getJarEntry(name);
90              assertNotNull(entry2, "Missing Entry: " + name);
91          }
92      }
93  
94      @Test
95      public void testDeflateHint() {
96          // Test default first
97          final PackingOptions options = new PackingOptions();
98          assertEquals("keep", options.getDeflateHint());
99          options.setDeflateHint("true");
100         assertEquals("true", options.getDeflateHint());
101         options.setDeflateHint("false");
102         assertEquals("false", options.getDeflateHint());
103         assertThrows(IllegalArgumentException.class, () -> options.setDeflateHint("hello"), "Should throw IllegalArgumentException for incorrect deflate hint");
104     }
105 
106     @Test
107     public void testErrorAttributes() throws Exception {
108         final File file = createTempFile("unknown", ".pack");
109         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI()));
110                 FileOutputStream out = new FileOutputStream(file)) {
111             final PackingOptions options = new PackingOptions();
112             options.addClassAttributeAction("Pack200", "error");
113             final Archive ar = new Archive(in, out, options);
114             final Error error = assertThrows(Error.class, () -> {
115                 ar.pack();
116                 in.close();
117                 out.close();
118             });
119             assertEquals("Attribute Pack200 was found", error.getMessage());
120         }
121     }
122 
123     @Test
124     public void testKeepFileOrder() throws Exception {
125         // Test default first
126         PackingOptions options = new PackingOptions();
127         assertTrue(options.isKeepFileOrder());
128         options.setKeepFileOrder(false);
129         assertFalse(options.isKeepFileOrder());
130 
131         // Test option works correctly. Test 'True'.
132         File file = createTempFile("sql", ".pack");
133         try (JarFile jarFile = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
134                 FileOutputStream outputStream = new FileOutputStream(file)) {
135             options = new PackingOptions();
136             options.setGzip(false);
137             new Archive(jarFile, outputStream, options).pack();
138         }
139 
140         File file2 = createTempFile("sql", ".jar");
141         unpackJar(file, file2);
142 
143         File compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
144         try (JarFile jarFile = new JarFile(file2);
145                 JarFile jarFile2 = new JarFile(compareFile)) {
146             // Check that both jars have the same entries in the same order
147             final Enumeration<JarEntry> entries = jarFile.entries();
148             final Enumeration<JarEntry> entries2 = jarFile2.entries();
149             while (entries.hasMoreElements()) {
150                 final JarEntry entry = entries.nextElement();
151                 assertNotNull(entry);
152                 final JarEntry entry2 = entries2.nextElement();
153                 final String name = entry.getName();
154                 final String name2 = entry2.getName();
155                 assertEquals(name, name2);
156             }
157         }
158         // Test 'false'
159         file = createTempFile("sql", ".pack");
160         try (JarFile jarFile = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
161                 FileOutputStream outputStream = new FileOutputStream(file);) {
162             options = new PackingOptions();
163             options.setKeepFileOrder(false);
164             options.setGzip(false);
165             new Archive(jarFile, outputStream, options).pack();
166         }
167 
168         file2 = createTempFile("sql", ".jar");
169         unpackJar(file, file2);
170 
171         compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
172         try (JarFile jarFile = new JarFile(file2);
173                 JarFile jarFile2 = new JarFile(compareFile)) {
174             // Check that both jars have the same entries (may be in a different order)
175             compareJarEntries(jarFile, jarFile2);
176             // Check files are not in order this time
177             final Enumeration<JarEntry> entries = jarFile.entries();
178             final Enumeration<JarEntry> entries2 = jarFile2.entries();
179             boolean inOrder = true;
180             while (entries.hasMoreElements()) {
181                 final JarEntry entry = entries.nextElement();
182                 assertNotNull(entry);
183                 final JarEntry entry2 = entries2.nextElement();
184                 final String name = entry.getName();
185                 final String name2 = entry2.getName();
186                 if (!name.equals(name2)) {
187                     inOrder = false;
188                     break;
189                 }
190             }
191             assertFalse(inOrder, "Files are not expected to be in order");
192         }
193     }
194 
195     // Test verbose, quiet and log file options.
196     @Test
197     public void testLoggingOptions() throws Exception {
198         // Test defaults
199         final PackingOptions options = new PackingOptions();
200         assertFalse(options.isVerbose());
201         assertNull(options.getLogFile());
202         options.setVerbose(true);
203         assertTrue(options.isVerbose());
204         options.setQuiet(true);
205         assertFalse(options.isVerbose());
206 
207         final File logFile = createTempFile("logfile", ".txt");
208         options.setLogFile(logFile.getPath());
209         assertEquals(logFile.getPath(), options.getLogFile());
210 
211         File file = createTempFile("helloworld", ".pack.gz");
212         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
213                 FileOutputStream out = new FileOutputStream(file)) {
214             new Archive(in, out, options).pack();
215         }
216 
217         // log file should be empty
218         try (FileReader reader = new FileReader(logFile)) {
219             assertFalse(reader.ready());
220         }
221 
222         options.setVerbose(true);
223         file = createTempFile("helloworld", ".pack.gz");
224         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
225                 FileOutputStream out = new FileOutputStream(file)) {
226             new Archive(in, out, options).pack();
227         }
228 
229         // log file should not be empty
230         try (FileReader reader = new FileReader(logFile)) {
231             assertTrue(reader.ready());
232         }
233         PackingUtils.config(null);
234     }
235 
236     @Test
237     public void testModificationTime() throws Exception {
238         // Test default first
239         PackingOptions options = new PackingOptions();
240         assertEquals("keep", options.getModificationTime());
241         options.setModificationTime("latest");
242         assertEquals("latest", options.getModificationTime());
243         assertThrows(IllegalArgumentException.class, () -> {
244             final PackingOptions illegalOption = new PackingOptions();
245             illegalOption.setModificationTime("true");
246         }, "Should throw IllegalArgumentException for incorrect mod time");
247 
248         // Test option works correctly. Test 'keep'.
249         File file = createTempFile("sql", ".pack");
250         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
251                 FileOutputStream out = new FileOutputStream(file)) {
252             options = new PackingOptions();
253             options.setGzip(false);
254             new Archive(in, out, options).pack();
255         }
256 
257         File file2 = createTempFile("sql", ".jar");
258         unpackJar(file, file2);
259 
260         File compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
261         try (JarFile jarFile = new JarFile(file2);
262                 JarFile jarFile2 = new JarFile(compareFile)) {
263             // Check that both jars have the same entries in the same order
264             final Enumeration<JarEntry> entries = jarFile.entries();
265             final Enumeration<JarEntry> entries2 = jarFile2.entries();
266             while (entries.hasMoreElements()) {
267 
268                 final JarEntry entry = entries.nextElement();
269                 assertNotNull(entry);
270                 final JarEntry entry2 = entries2.nextElement();
271                 final String name = entry.getName();
272                 final String name2 = entry2.getName();
273                 assertEquals(name, name2);
274                 assertEquals(entry.getTime(), entry2.getTime());
275             }
276         }
277         // Test option works correctly. Test 'latest'.
278         file = createTempFile("sql", ".pack");
279         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
280                 FileOutputStream out = new FileOutputStream(file)) {
281             options = new PackingOptions();
282             options.setGzip(false);
283             options.setModificationTime("latest");
284             new Archive(in, out, options).pack();
285         }
286 
287         file2 = createTempFile("sql", ".jar");
288         unpackJar(file, file2);
289 
290         compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
291         try (JarFile jarFile = new JarFile(file2);
292                 JarFile jarFile2 = new JarFile(compareFile)) {
293             // Check that all mod times are the same and some are not the same as the original
294             final Enumeration<JarEntry> entries = jarFile.entries();
295             final Enumeration<JarEntry> entries2 = jarFile2.entries();
296             long modtime = -1;
297             boolean sameAsOriginal = true;
298             while (entries.hasMoreElements()) {
299                 final JarEntry entry = entries.nextElement();
300                 assertNotNull(entry);
301                 final JarEntry entry2 = entries2.nextElement();
302                 final String name = entry.getName();
303                 if (!name.startsWith("META-INF")) {
304                     if (modtime == -1) {
305                         modtime = entry.getTime();
306                     } else {
307                         assertEquals(modtime, entry.getTime());
308                     }
309                 }
310                 if (entry2.getTime() != entry.getTime()) {
311                     sameAsOriginal = false;
312                 }
313             }
314             assertFalse(sameAsOriginal, "Some modtimes should have changed");
315         }
316     }
317 
318     @Test
319     public void testNewAttributes() throws Exception {
320         final File file = createTempFile("unknown", ".pack");
321         try (FileOutputStream out = new FileOutputStream(file);
322                 JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI()))) {
323             final PackingOptions options = new PackingOptions();
324             options.addClassAttributeAction("Pack200", "I");
325             new Archive(in, out, options).pack();
326         }
327 
328         // unpack and check this was done right
329         final File file2 = createTempFile("unknown", ".jar");
330         unpackJar(file, file2);
331         // compare with original
332         final File compareFile = new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI());
333         try (JarFile jarFile = new JarFile(file2);
334                 JarFile jarFile2 = new JarFile(compareFile)) {
335             assertEquals(jarFile2.size(), jarFile.size());
336             compareJarEntries(jarFile, jarFile2);
337 //        compareFiles(jarFile, jarFile2);
338         }
339     }
340 
341     @Test
342     public void testNewAttributes2() throws Exception {
343         final File file = createTempFile("unknown", ".pack");
344         try (FileOutputStream out = new FileOutputStream(file);
345                 JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/p200WithUnknownAttributes.jar").toURI()))) {
346             final PackingOptions options = new PackingOptions();
347             options.addFieldAttributeAction("Pack200", "I");
348             options.addMethodAttributeAction("Pack200", "I");
349             options.addCodeAttributeAction("Pack200", "I");
350             final Archive ar = new Archive(in, out, options);
351             ar.pack();
352         }
353         // unpack and check this was done right
354         final File file2 = createTempFile("unknown", ".jar");
355         unpackJar(file, file2);
356 
357         // compare with original
358         final File compareFile = new File(Archive.class.getResource("/pack200/p200WithUnknownAttributes.jar").toURI());
359         try (JarFile jarFile = new JarFile(file2);
360                 JarFile jarFile2 = new JarFile(compareFile)) {
361             assertEquals(jarFile2.size(), jarFile.size());
362             compareJarEntries(jarFile, jarFile2);
363         }
364     }
365 
366     @Test
367     public void testPackEffort0() throws Pack200Exception, IOException, URISyntaxException {
368         final File f1 = new File(Archive.class.getResource("/pack200/jndi.jar").toURI());
369         final File file = createTempFile("jndiE0", ".pack");
370         try (JarFile in = new JarFile(f1);
371                 FileOutputStream out = new FileOutputStream(file)) {
372             final PackingOptions options = new PackingOptions();
373             options.setGzip(false);
374             options.setEffort(0);
375             new Archive(in, out, options).pack();
376         }
377         try (JarFile jf1 = new JarFile(f1);
378                 JarFile jf2 = new JarFile(file)) {
379             compareFiles(jf1, jf2);
380         }
381     }
382 
383     @Test
384     public void testPassAttributes() throws Exception {
385         final File file = createTempFile("unknown", ".pack");
386         try (FileOutputStream out = new FileOutputStream(file);
387                 JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI()))) {
388             final PackingOptions options = new PackingOptions();
389             options.addClassAttributeAction("Pack200", "pass");
390             new Archive(in, out, options).pack();
391         }
392 
393         // now unpack
394         final File file2 = createTempFile("unknown", ".jar");
395         unpackJar(file, file2);
396 
397         // compare with original
398         final File compareFile = new File(Archive.class.getResource("/pack200/jndiWithUnknownAttributes.jar").toURI());
399         try (JarFile jarFile = new JarFile(file2);
400                 JarFile jarFile2 = new JarFile(compareFile)) {
401             assertEquals(jarFile2.size(), jarFile.size());
402             compareJarEntries(jarFile, jarFile2);
403         }
404     }
405 
406     @Test
407     public void testPassFiles() throws IOException, URISyntaxException, Pack200Exception {
408         // Don't pass any
409         final File file0 = createTempFile("sql", ".pack");
410         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
411                 FileOutputStream out = new FileOutputStream(file0)) {
412             final PackingOptions options = new PackingOptions();
413             options.setGzip(false);
414             new Archive(in, out, options).pack();
415         }
416 
417         // Pass one file
418         final File file = createTempFile("sql", ".pack");
419         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
420                 FileOutputStream out = new FileOutputStream(file)) {
421             final PackingOptions options = new PackingOptions();
422             options.setGzip(false);
423             options.addPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class");
424             assertTrue(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class"));
425             new Archive(in, out, options).pack();
426         }
427 
428         // Pass a whole directory
429         final File file2 = createTempFile("sql", ".pack");
430         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
431                 FileOutputStream out = new FileOutputStream(file2)) {
432             final PackingOptions options = new PackingOptions();
433             options.setGzip(false);
434             options.addPassFile("bin/test/org/apache/harmony/sql/tests/java/sql");
435             assertTrue(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sql/DatabaseMetaDataTest.class"));
436             assertFalse(options.isPassFile("bin/test/org/apache/harmony/sql/tests/java/sqldata/SqlData.class"));
437             new Archive(in, out, options).pack();
438         }
439 
440         assertTrue(file.length() > file0.length(), "If files are passed then the pack file should be larger");
441         assertTrue(file2.length() > file.length(), "If more files are passed then the pack file should be larger");
442 
443         // now unpack
444         final File file3 = createTempFile("sql", ".jar");
445         unpackJar(file, file3);
446 
447         final File compareFile = new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI());
448         try (JarFile jarFile = new JarFile(file3);
449                 JarFile jarFile2 = new JarFile(compareFile)) {
450             // Check that both jars have the same entries
451             compareJarEntries(jarFile, jarFile2);
452         }
453         // now unpack the file with lots of passed files
454         final File file4 = createTempFile("sql", ".jar");
455         unpackJar(file2, file4);
456 
457         try (JarFile jarFile = new JarFile(file4);
458                 JarFile jarFile2 = new JarFile(compareFile)) {
459             compareJarEntries(jarFile, jarFile2);
460         }
461     }
462 
463     @Test
464     public void testSegmentLimits() throws IOException, Pack200Exception, URISyntaxException {
465         File file = createTempFile("helloworld", ".pack.gz");
466         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
467                 FileOutputStream out = new FileOutputStream(file)) {
468             final PackingOptions options = new PackingOptions();
469             options.setSegmentLimit(0);
470             final Archive archive = new Archive(in, out, options);
471             archive.pack();
472         }
473 
474         file = createTempFile("helloworld", ".pack.gz");
475         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
476                 FileOutputStream out = new FileOutputStream(file)) {
477             final PackingOptions options = new PackingOptions();
478             options.setSegmentLimit(-1);
479             new Archive(in, out, options).pack();
480         }
481 
482         file = createTempFile("helloworld", ".pack.gz");
483         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/hw.jar").toURI()));
484                 FileOutputStream out = new FileOutputStream(file)) {
485             final PackingOptions options = new PackingOptions();
486             options.setSegmentLimit(5000);
487             new Archive(in, out, options).pack();
488         }
489     }
490 
491     @Test
492     public void testStripDebug() throws IOException, Pack200Exception, URISyntaxException {
493         final File file = createTempFile("sql", ".pack");
494         try (JarFile in = new JarFile(new File(Archive.class.getResource("/pack200/sqlUnpacked.jar").toURI()));
495                 FileOutputStream out = new FileOutputStream(file)) {
496             final PackingOptions options = new PackingOptions();
497             options.setGzip(false);
498             options.setStripDebug(true);
499             final Archive archive = new Archive(in, out, options);
500             archive.pack();
501         }
502 
503         // now unpack
504         final File file2 = createTempFile("sqloutNoDebug", ".jar");
505         unpackJar(file, file2);
506 
507         final File compareFile = new File(Archive.class.getResource("/pack200/sqlUnpackedNoDebug.jar").toURI());
508         try (JarFile jarFile = new JarFile(file2);
509                 JarFile jarFile2 = new JarFile(compareFile)) {
510             assertTrue(file2.length() < 250000);
511             compareFiles(jarFile, jarFile2);
512         }
513     }
514 
515     // public void testE0again() throws IOException, Pack200Exception,
516     // URISyntaxException {
517     // JarInputStream inputStream = new
518     // JarInputStream(Archive.class.getResourceAsStream("/pack200/jndi.jar"));
519     // file = File.createTempFile("jndiE0", ".pack");
520     // out = new FileOutputStream(file);
521     // Archive archive = new Archive(inputStream, out, false);
522     // archive.setEffort(0);
523     // archive.pack();
524     // inputStream.close();
525     // out.close();
526     // in = new JarFile(new File(Archive.class.getResource(
527     // "/pack200/jndi.jar").toURI()));
528     // compareFiles(in, new JarFile(file));
529     // }
530 
531     private void unpack(final InputStream inputStream, final JarOutputStream outputStream) throws Pack200Exception, IOException {
532         new org.apache.commons.compress.harmony.unpack200.Archive(inputStream, outputStream).unpack();
533     }
534 
535     private void unpackJar(final File sourceFile, final File destFile) throws Pack200Exception, IOException, FileNotFoundException {
536         try (InputStream in2 = new FileInputStream(sourceFile);
537                 JarOutputStream out2 = new JarOutputStream(new FileOutputStream(destFile))) {
538             unpack(in2, out2);
539         }
540     }
541 
542 }