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