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.archivers.sevenz;
18  
19  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotEquals;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.File;
29  import java.io.IOException;
30  import java.nio.file.Files;
31  import java.nio.file.Paths;
32  import java.nio.file.attribute.FileTime;
33  import java.time.Instant;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Calendar;
37  import java.util.Collections;
38  import java.util.Date;
39  import java.util.Iterator;
40  
41  import org.apache.commons.compress.AbstractTest;
42  import org.apache.commons.compress.PasswordRequiredException;
43  import org.apache.commons.compress.utils.ByteUtils;
44  import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
45  import org.apache.commons.compress.utils.TimeUtils;
46  import org.junit.jupiter.api.Test;
47  import org.tukaani.xz.LZMA2Options;
48  
49  public class SevenZOutputFileTest extends AbstractTest {
50  
51      private static final boolean XZ_BCJ_IS_BUGGY;
52  
53      static {
54          final String version = org.tukaani.xz.XZ.class.getPackage().getImplementationVersion();
55  
56          XZ_BCJ_IS_BUGGY = version != null && version.equals("1.4");
57          if (XZ_BCJ_IS_BUGGY) {
58              System.out.println("XZ version is " + version + " - skipping BCJ tests");
59          }
60      }
61  
62      private static void assertContentMethodsEquals(final Iterable<? extends SevenZMethodConfiguration> expected,
63              final Iterable<? extends SevenZMethodConfiguration> actual) {
64          assertNotNull(actual);
65          final Iterator<? extends SevenZMethodConfiguration> actualIter = actual.iterator();
66          for (final SevenZMethodConfiguration expConfig : expected) {
67              assertTrue(actualIter.hasNext());
68              final SevenZMethodConfiguration actConfig = actualIter.next();
69              assertEquals(expConfig.getMethod(), actConfig.getMethod());
70          }
71          assertFalse(actualIter.hasNext());
72      }
73  
74      private void addDir(final SevenZOutputFile archive) throws Exception {
75          final SevenZArchiveEntry entry = archive.createArchiveEntry(getTempDirFile(), "foo/");
76          archive.putArchiveEntry(entry);
77          archive.closeArchiveEntry();
78      }
79  
80      private void addFile(final SevenZOutputFile archive, final int index, final boolean nonEmpty) throws Exception {
81          addFile(archive, index, nonEmpty, null);
82      }
83  
84      private void addFile(final SevenZOutputFile archive, final int index, final boolean nonEmpty, final Iterable<SevenZMethodConfiguration> methods)
85              throws Exception {
86          addFile(archive, index, nonEmpty ? 1 : 0, methods);
87      }
88  
89      private void addFile(final SevenZOutputFile archive, final int index, final int size, final Iterable<SevenZMethodConfiguration> methods) throws Exception {
90          final SevenZArchiveEntry entry = new SevenZArchiveEntry();
91          entry.setName("foo/" + index + ".txt");
92          entry.setContentMethods(methods);
93          archive.putArchiveEntry(entry);
94          archive.write(generateFileData(size));
95          archive.closeArchiveEntry();
96      }
97  
98      private void createAndReadBack(final File output, final Iterable<SevenZMethodConfiguration> methods) throws Exception {
99          try (SevenZOutputFile outArchive = new SevenZOutputFile(output)) {
100             outArchive.setContentMethods(methods);
101             addFile(outArchive, 0, true);
102         }
103 
104         try (SevenZFile archive = SevenZFile.builder().setFile(output).get()) {
105             assertEquals(Boolean.TRUE, verifyFile(archive, 0, methods));
106         }
107     }
108 
109     private void createAndReadBack(final SeekableInMemoryByteChannel output, final Iterable<SevenZMethodConfiguration> methods) throws Exception {
110         try (SevenZOutputFile outArchive = new SevenZOutputFile(output)) {
111             outArchive.setContentMethods(methods);
112             addFile(outArchive, 0, true);
113         }
114         try (SevenZFile archive = SevenZFile.builder().setByteArray(output.array()).setDefaultName("in memory").get()) {
115             assertEquals(Boolean.TRUE, verifyFile(archive, 0, methods));
116         }
117     }
118 
119     private byte[] generateFileData(final int size) {
120         final byte[] data = new byte[size];
121         for (int i = 0; i < size; i++) {
122             data[i] = (byte) ('A' + i % 26);
123         }
124         return data;
125     }
126 
127     private FileTime getHundredNanosFileTime() {
128         final Instant now = Instant.now();
129         // In some platforms, Java's Instant has a precision of milliseconds.
130         // Add some nanos at the end to test 100ns intervals.
131         final FileTime fileTime = FileTime.from(Instant.ofEpochSecond(now.getEpochSecond(), now.getNano() + 999900));
132         // However, in some platforms, Java's Instant has a precision of nanoseconds.
133         // Truncate the resulting FileTime to 100ns intervals.
134         return TimeUtils.truncateToHundredNanos(fileTime);
135     }
136 
137     @Test
138     public void testArchiveWithMixedMethods() throws Exception {
139         final File output = newTempFile("mixed-methods.7z");
140         try (SevenZOutputFile outArchive = new SevenZOutputFile(output)) {
141             addFile(outArchive, 0, true);
142             addFile(outArchive, 1, true, Arrays.asList(new SevenZMethodConfiguration(SevenZMethod.BZIP2)));
143         }
144 
145         try (SevenZFile archive = SevenZFile.builder().setFile(output).get()) {
146             assertEquals(Boolean.TRUE, verifyFile(archive, 0, Arrays.asList(new SevenZMethodConfiguration(SevenZMethod.LZMA2))));
147             assertEquals(Boolean.TRUE, verifyFile(archive, 1, Arrays.asList(new SevenZMethodConfiguration(SevenZMethod.BZIP2))));
148         }
149     }
150 
151     @Test
152     public void testBCJARMRoundtrip() throws Exception {
153         if (XZ_BCJ_IS_BUGGY) {
154             return;
155         }
156         testFilterRoundTrip(new SevenZMethodConfiguration(SevenZMethod.BCJ_ARM_FILTER));
157     }
158 
159     @Test
160     public void testBCJARMThumbRoundtrip() throws Exception {
161         if (XZ_BCJ_IS_BUGGY) {
162             return;
163         }
164         testFilterRoundTrip(new SevenZMethodConfiguration(SevenZMethod.BCJ_ARM_THUMB_FILTER));
165     }
166 
167     @Test
168     public void testBCJIA64Roundtrip() throws Exception {
169         if (XZ_BCJ_IS_BUGGY) {
170             return;
171         }
172         testFilterRoundTrip(new SevenZMethodConfiguration(SevenZMethod.BCJ_IA64_FILTER));
173     }
174 
175     @Test
176     public void testBCJPPCRoundtrip() throws Exception {
177         if (XZ_BCJ_IS_BUGGY) {
178             return;
179         }
180         testFilterRoundTrip(new SevenZMethodConfiguration(SevenZMethod.BCJ_PPC_FILTER));
181     }
182 
183     @Test
184     public void testBCJSparcRoundtrip() throws Exception {
185         if (XZ_BCJ_IS_BUGGY) {
186             return;
187         }
188         testFilterRoundTrip(new SevenZMethodConfiguration(SevenZMethod.BCJ_SPARC_FILTER));
189     }
190 
191     @Test
192     public void testBCJX86Roundtrip() throws Exception {
193         if (XZ_BCJ_IS_BUGGY) {
194             return;
195         }
196         testFilterRoundTrip(new SevenZMethodConfiguration(SevenZMethod.BCJ_X86_FILTER));
197     }
198 
199     @Test
200     public void testBzip2Roundtrip() throws Exception {
201         testRoundTrip(SevenZMethod.BZIP2);
202     }
203 
204     @Test
205     public void testBzip2WithConfiguration() throws Exception {
206         final File output = newTempFile("bzip2-options.7z");
207         // 400k block size
208         createAndReadBack(output, Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.BZIP2, 4)));
209     }
210 
211     @Test
212     public void testCantFinishTwice() throws IOException {
213         final File output = newTempFile("finish.7z");
214         try (SevenZOutputFile outArchive = new SevenZOutputFile(output)) {
215             outArchive.finish();
216             final IOException ex = assertThrows(IOException.class, outArchive::finish, "shouldn't be able to call finish twice");
217             assertEquals("This archive has already been finished", ex.getMessage());
218         }
219     }
220 
221     private void testCompress252(final int numberOfFiles, final int numberOfNonEmptyFiles) throws Exception {
222         final int nonEmptyModulus = numberOfNonEmptyFiles != 0 ? numberOfFiles / numberOfNonEmptyFiles : numberOfFiles + 1;
223         int nonEmptyFilesAdded = 0;
224         final File output = newTempFile("COMPRESS252-" + numberOfFiles + "-" + numberOfNonEmptyFiles + ".7z");
225         try (SevenZOutputFile archive = new SevenZOutputFile(output)) {
226             addDir(archive);
227             for (int i = 0; i < numberOfFiles; i++) {
228                 addFile(archive, i, (i + 1) % nonEmptyModulus == 0 && nonEmptyFilesAdded++ < numberOfNonEmptyFiles);
229             }
230         }
231         verifyCompress252(output, numberOfFiles, numberOfNonEmptyFiles);
232     }
233 
234     @Test
235     public void testCopyRoundtrip() throws Exception {
236         testRoundTrip(SevenZMethod.COPY);
237     }
238 
239     @Test
240     public void testDeflateRoundtrip() throws Exception {
241         testRoundTrip(SevenZMethod.DEFLATE);
242     }
243 
244     @Test
245     public void testDeflateWithConfiguration() throws Exception {
246         final File output = newTempFile("deflate-options.7z");
247         // Deflater.BEST_SPEED
248         createAndReadBack(output, Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.DEFLATE, 1)));
249     }
250 
251     @Test
252     public void testDeltaRoundtrip() throws Exception {
253         testFilterRoundTrip(new SevenZMethodConfiguration(SevenZMethod.DELTA_FILTER));
254     }
255 
256     @Test
257     public void testDirectoriesAndEmptyFiles() throws Exception {
258         final File output = newTempFile("empties.7z");
259 
260         final FileTime accessTime = getHundredNanosFileTime();
261         final Date accessDate = new Date(accessTime.toMillis());
262         final Calendar cal = Calendar.getInstance();
263         cal.add(Calendar.HOUR, -1);
264         final Date creationDate = cal.getTime();
265 
266         try (SevenZOutputFile outArchive = new SevenZOutputFile(output)) {
267             SevenZArchiveEntry entry = outArchive.createArchiveEntry(getTempDirFile(), "foo/");
268             outArchive.putArchiveEntry(entry);
269             outArchive.closeArchiveEntry();
270 
271             entry = new SevenZArchiveEntry();
272             entry.setName("foo/bar");
273             entry.setCreationDate(creationDate);
274             entry.setAccessTime(accessTime);
275             outArchive.putArchiveEntry(entry);
276             outArchive.write(ByteUtils.EMPTY_BYTE_ARRAY);
277             outArchive.closeArchiveEntry();
278 
279             entry = new SevenZArchiveEntry();
280             entry.setName("foo/bar/boo0");
281             entry.setCreationDate(creationDate);
282             entry.setAccessTime(accessTime);
283             outArchive.putArchiveEntry(entry);
284             outArchive.write(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY));
285             outArchive.closeArchiveEntry();
286 
287             entry = new SevenZArchiveEntry();
288             entry.setName("foo/bar/boo1");
289             entry.setCreationDate(creationDate);
290             entry.setAccessTime(accessTime);
291             outArchive.putArchiveEntry(entry);
292             outArchive.write(new ByteArrayInputStream(new byte[] { 'a' }));
293             outArchive.closeArchiveEntry();
294 
295             entry = new SevenZArchiveEntry();
296             entry.setName("foo/bar/boo10000");
297             entry.setCreationDate(creationDate);
298             entry.setAccessTime(accessTime);
299             outArchive.putArchiveEntry(entry);
300             outArchive.write(new ByteArrayInputStream(new byte[10000]));
301             outArchive.closeArchiveEntry();
302 
303             entry = new SevenZArchiveEntry();
304             entry.setName("foo/bar/test.txt");
305             entry.setCreationDate(creationDate);
306             entry.setAccessTime(accessTime);
307             outArchive.putArchiveEntry(entry);
308             outArchive.write(Paths.get("src/test/resources/test.txt"));
309             outArchive.closeArchiveEntry();
310 
311             entry = new SevenZArchiveEntry();
312             entry.setName("xyzzy");
313             outArchive.putArchiveEntry(entry);
314             outArchive.write(0);
315             outArchive.closeArchiveEntry();
316 
317             entry = outArchive.createArchiveEntry(getTempDirFile(), "baz/");
318             entry.setAntiItem(true);
319             outArchive.putArchiveEntry(entry);
320             outArchive.closeArchiveEntry();
321 
322             entry = outArchive.createArchiveEntry(getTempDirFile().toPath(), "baz2/");
323             entry.setAntiItem(true);
324             outArchive.putArchiveEntry(entry);
325             outArchive.closeArchiveEntry();
326 
327             entry = new SevenZArchiveEntry();
328             entry.setName("dada");
329             entry.setHasWindowsAttributes(true);
330             entry.setWindowsAttributes(17);
331             outArchive.putArchiveEntry(entry);
332             outArchive.write(5);
333             outArchive.write(42);
334             outArchive.closeArchiveEntry();
335 
336             outArchive.finish();
337         }
338 
339         try (SevenZFile archive = SevenZFile.builder().setFile(output).get()) {
340             SevenZArchiveEntry entry = archive.getNextEntry();
341             assert entry != null;
342             assertEquals("foo/", entry.getName());
343             assertTrue(entry.isDirectory());
344             assertFalse(entry.isAntiItem());
345 
346             entry = archive.getNextEntry();
347             assert entry != null;
348             assertEquals("foo/bar", entry.getName());
349             assertFalse(entry.isDirectory());
350             assertFalse(entry.isAntiItem());
351             assertEquals(0, entry.getSize());
352             assertFalse(entry.getHasLastModifiedDate());
353             assertEquals(accessTime, entry.getAccessTime());
354             assertEquals(accessDate, entry.getAccessDate());
355             assertEquals(creationDate, entry.getCreationDate());
356 
357             entry = archive.getNextEntry();
358             assert entry != null;
359             assertEquals("foo/bar/boo0", entry.getName());
360             assertFalse(entry.isDirectory());
361             assertFalse(entry.isAntiItem());
362             assertEquals(0, entry.getSize());
363             assertFalse(entry.getHasLastModifiedDate());
364             assertEquals(accessTime, entry.getAccessTime());
365             assertEquals(accessDate, entry.getAccessDate());
366             assertEquals(creationDate, entry.getCreationDate());
367 
368             entry = archive.getNextEntry();
369             assert entry != null;
370             assertEquals("foo/bar/boo1", entry.getName());
371             assertFalse(entry.isDirectory());
372             assertFalse(entry.isAntiItem());
373             assertEquals(1, entry.getSize());
374             assertFalse(entry.getHasLastModifiedDate());
375             assertEquals(accessTime, entry.getAccessTime());
376             assertEquals(accessDate, entry.getAccessDate());
377             assertEquals(creationDate, entry.getCreationDate());
378 
379             entry = archive.getNextEntry();
380             assert entry != null;
381             assertEquals("foo/bar/boo10000", entry.getName());
382             assertFalse(entry.isDirectory());
383             assertFalse(entry.isAntiItem());
384             assertEquals(10000, entry.getSize());
385             assertFalse(entry.getHasLastModifiedDate());
386             assertEquals(accessTime, entry.getAccessTime());
387             assertEquals(accessDate, entry.getAccessDate());
388             assertEquals(creationDate, entry.getCreationDate());
389 
390             entry = archive.getNextEntry();
391             assert entry != null;
392             assertEquals("foo/bar/test.txt", entry.getName());
393             assertFalse(entry.isDirectory());
394             assertFalse(entry.isAntiItem());
395             assertEquals(Files.size(Paths.get("src/test/resources/test.txt")), entry.getSize());
396             assertFalse(entry.getHasLastModifiedDate());
397             assertEquals(accessTime, entry.getAccessTime());
398             assertEquals(accessDate, entry.getAccessDate());
399             assertEquals(creationDate, entry.getCreationDate());
400 
401             entry = archive.getNextEntry();
402             assert entry != null;
403             assertEquals("xyzzy", entry.getName());
404             assertEquals(1, entry.getSize());
405             assertFalse(entry.getHasAccessDate());
406             assertFalse(entry.getHasCreationDate());
407             assertEquals(0, archive.read());
408 
409             entry = archive.getNextEntry();
410             assert entry != null;
411             assertEquals("baz/", entry.getName());
412             assertTrue(entry.isDirectory());
413             assertTrue(entry.isAntiItem());
414 
415             entry = archive.getNextEntry();
416             assert entry != null;
417             assertEquals("baz2/", entry.getName());
418             assertTrue(entry.isDirectory());
419             assertTrue(entry.isAntiItem());
420 
421             entry = archive.getNextEntry();
422             assert entry != null;
423             assertEquals("dada", entry.getName());
424             assertEquals(2, entry.getSize());
425             final byte[] content = new byte[2];
426             assertEquals(2, archive.read(content));
427             assertEquals(5, content[0]);
428             assertEquals(42, content[1]);
429             assertEquals(17, entry.getWindowsAttributes());
430 
431             assert archive.getNextEntry() == null;
432         }
433 
434     }
435 
436     @Test
437     public void testDirectoriesOnly() throws Exception {
438         final File output = newTempFile("dirs.7z");
439         try (SevenZOutputFile outArchive = new SevenZOutputFile(output)) {
440             final SevenZArchiveEntry entry = new SevenZArchiveEntry();
441             entry.setName("foo/");
442             entry.setDirectory(true);
443             outArchive.putArchiveEntry(entry);
444             outArchive.closeArchiveEntry();
445         }
446 
447         try (SevenZFile archive = SevenZFile.builder().setFile(output).get()) {
448             final SevenZArchiveEntry entry = archive.getNextEntry();
449             assert entry != null;
450             assertEquals("foo/", entry.getName());
451             assertTrue(entry.isDirectory());
452             assertFalse(entry.isAntiItem());
453 
454             assert archive.getNextEntry() == null;
455         }
456 
457     }
458 
459     @Test
460     public void testEightEmptyFiles() throws Exception {
461         testCompress252(8, 0);
462     }
463 
464     @Test
465     public void testEightFilesSomeNotEmpty() throws Exception {
466         testCompress252(8, 2);
467     }
468 
469     /**
470      * Test password-based encryption
471      *
472      * <p>
473      * As AES/CBC Cipher requires a minimum of 16 bytes file data to be encrypted, some padding logic has been implemented. This test checks different file
474      * sizes (1, 16..) to ensure code coverage
475      * </p>
476      */
477     @Test
478     public void testEncrypt() throws Exception {
479         final File output = newTempFile("encrypted.7z");
480         try (SevenZOutputFile outArchive = new SevenZOutputFile(output, "foo".toCharArray())) {
481             addFile(outArchive, 0, 1, null);
482             addFile(outArchive, 1, 16, null);
483             addFile(outArchive, 2, 32, null);
484             addFile(outArchive, 3, 33, null);
485             addFile(outArchive, 4, 10000, null);
486         }
487 
488         // Is archive really password-based encrypted ?
489         try (SevenZFile archive = SevenZFile.builder().setFile(output).get()) {
490             assertThrows(
491                     PasswordRequiredException.class, () -> verifyFile(archive, 0), "A password should be needed");
492         }
493 
494         try (SevenZFile archive = SevenZFile.builder().setFile(output).setPassword("foo").get()) {
495             assertEquals(Boolean.TRUE, verifyFile(archive, 0, 1, null));
496             assertEquals(Boolean.TRUE, verifyFile(archive, 1, 16, null));
497             assertEquals(Boolean.TRUE, verifyFile(archive, 2, 32, null));
498             assertEquals(Boolean.TRUE, verifyFile(archive, 3, 33, null));
499             assertEquals(Boolean.TRUE, verifyFile(archive, 4, 10000, null));
500         }
501     }
502 
503     private void testFilterRoundTrip(final SevenZMethodConfiguration method) throws Exception {
504         final File output = newTempFile(method.getMethod() + "-roundtrip.7z");
505         final ArrayList<SevenZMethodConfiguration> methods = new ArrayList<>();
506         methods.add(method);
507         methods.add(new SevenZMethodConfiguration(SevenZMethod.LZMA2));
508         createAndReadBack(output, methods);
509     }
510 
511     @Test
512     public void testLzma2Roundtrip() throws Exception {
513         testRoundTrip(SevenZMethod.LZMA2);
514     }
515 
516     @Test
517     public void testLzma2WithIntConfiguration() throws Exception {
518         final File output = newTempFile("lzma2-options.7z");
519         // 1 MB dictionary
520         createAndReadBack(output, Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA2, 1 << 20)));
521     }
522 
523     @Test
524     public void testLzma2WithOptionsConfiguration() throws Exception {
525         final File output = newTempFile("lzma2-options2.7z");
526         final LZMA2Options opts = new LZMA2Options(1);
527         createAndReadBack(output, Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA2, opts)));
528     }
529 
530     @Test
531     public void testLzmaWithIntConfiguration() throws Exception {
532         final File output = newTempFile("lzma-options.7z");
533         // 1 MB dictionary
534         createAndReadBack(output, Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA, 1 << 20)));
535     }
536 
537     @Test
538     public void testLzmaWithOptionsConfiguration() throws Exception {
539         final File output = newTempFile("lzma-options2.7z");
540         final LZMA2Options opts = new LZMA2Options(1);
541         createAndReadBack(output, Collections.singletonList(new SevenZMethodConfiguration(SevenZMethod.LZMA, opts)));
542     }
543 
544     @Test
545     public void testNineEmptyFiles() throws Exception {
546         testCompress252(9, 0);
547     }
548 
549     @Test
550     public void testNineFilesSomeNotEmpty() throws Exception {
551         testCompress252(9, 2);
552     }
553 
554     private void testRoundTrip(final SevenZMethod method) throws Exception {
555         final File output = newTempFile(method + "-roundtrip.7z");
556         final ArrayList<SevenZMethodConfiguration> methods = new ArrayList<>();
557         methods.add(new SevenZMethodConfiguration(method));
558         createAndReadBack(output, methods);
559     }
560 
561     @Test
562     public void testSevenEmptyFiles() throws Exception {
563         testCompress252(7, 0);
564     }
565 
566     @Test
567     public void testSevenFilesSomeNotEmpty() throws Exception {
568         testCompress252(7, 2);
569     }
570 
571     @Test
572     public void testSixEmptyFiles() throws Exception {
573         testCompress252(6, 0);
574     }
575 
576     @Test
577     public void testSixFilesSomeNotEmpty() throws Exception {
578         testCompress252(6, 2);
579     }
580 
581     @Test
582     public void testStackOfContentCompressions() throws Exception {
583         final File output = newTempFile("multiple-methods.7z");
584         final ArrayList<SevenZMethodConfiguration> methods = new ArrayList<>();
585         methods.add(new SevenZMethodConfiguration(SevenZMethod.LZMA2));
586         methods.add(new SevenZMethodConfiguration(SevenZMethod.COPY));
587         methods.add(new SevenZMethodConfiguration(SevenZMethod.DEFLATE));
588         methods.add(new SevenZMethodConfiguration(SevenZMethod.BZIP2));
589         createAndReadBack(output, methods);
590     }
591 
592     @Test
593     public void testStackOfContentCompressionsInMemory() throws Exception {
594         final ArrayList<SevenZMethodConfiguration> methods = new ArrayList<>();
595         methods.add(new SevenZMethodConfiguration(SevenZMethod.LZMA2));
596         methods.add(new SevenZMethodConfiguration(SevenZMethod.COPY));
597         methods.add(new SevenZMethodConfiguration(SevenZMethod.DEFLATE));
598         methods.add(new SevenZMethodConfiguration(SevenZMethod.BZIP2));
599         try (SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel()) {
600             createAndReadBack(channel, methods);
601         }
602     }
603 
604     @Test
605     public void testTwentyNineEmptyFiles() throws Exception {
606         testCompress252(29, 0);
607     }
608 
609     @Test
610     public void testTwentyNineFilesSomeNotEmpty() throws Exception {
611         testCompress252(29, 7);
612     }
613 
614     private void verifyCompress252(final File output, final int numberOfFiles, final int numberOfNonEmptyFiles) throws Exception {
615         int filesFound = 0;
616         int nonEmptyFilesFound = 0;
617         try (SevenZFile archive = SevenZFile.builder().setFile(output).get()) {
618             verifyDir(archive);
619             Boolean b = verifyFile(archive, filesFound++);
620             while (b != null) {
621                 if (Boolean.TRUE.equals(b)) {
622                     nonEmptyFilesFound++;
623                 }
624                 b = verifyFile(archive, filesFound++);
625             }
626         }
627         assertEquals(numberOfFiles + 1, filesFound);
628         assertEquals(numberOfNonEmptyFiles, nonEmptyFilesFound);
629     }
630 
631     private void verifyDir(final SevenZFile archive) throws Exception {
632         final SevenZArchiveEntry entry = archive.getNextEntry();
633         assertNotNull(entry);
634         assertEquals("foo/", entry.getName());
635         assertTrue(entry.isDirectory());
636     }
637 
638     private Boolean verifyFile(final SevenZFile archive, final int index) throws Exception {
639         return verifyFile(archive, index, null);
640     }
641 
642     private Boolean verifyFile(final SevenZFile archive, final int index, final int size, final Iterable<SevenZMethodConfiguration> methods) throws Exception {
643         final SevenZArchiveEntry entry = archive.getNextEntry();
644         if (entry == null) {
645             return null;
646         }
647         assertEquals("foo/" + index + ".txt", entry.getName());
648         assertFalse(entry.isDirectory());
649         if (entry.getSize() == 0) {
650             return Boolean.FALSE;
651         }
652         assertEquals(size, entry.getSize());
653 
654         final byte[] actual = new byte[size];
655         int count = 0;
656         while (count < size) {
657             final int read = archive.read(actual, count, actual.length - count);
658             assertNotEquals(-1, read, "EOF reached before reading all expected data");
659             count += read;
660         }
661         assertArrayEquals(generateFileData(size), actual);
662         assertEquals(-1, archive.read());
663         if (methods != null) {
664             assertContentMethodsEquals(methods, entry.getContentMethods());
665         }
666         return Boolean.TRUE;
667     }
668 
669     private Boolean verifyFile(final SevenZFile archive, final int index, final Iterable<SevenZMethodConfiguration> methods) throws Exception {
670         return verifyFile(archive, index, 1, methods);
671     }
672 }