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   * http://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;
20  
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.fail;
23  
24  import java.io.BufferedInputStream;
25  import java.io.Closeable;
26  import java.io.File;
27  import java.io.FileNotFoundException;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.net.URISyntaxException;
32  import java.net.URL;
33  import java.nio.file.Files;
34  import java.nio.file.Path;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.List;
38  
39  import org.apache.commons.compress.archivers.ArchiveEntry;
40  import org.apache.commons.compress.archivers.ArchiveInputStream;
41  import org.apache.commons.compress.archivers.ArchiveOutputStream;
42  import org.apache.commons.compress.archivers.ArchiveStreamFactory;
43  import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
44  import org.apache.commons.io.FileUtils;
45  import org.apache.commons.io.IOUtils;
46  import org.junit.jupiter.api.io.TempDir;
47  
48  public abstract class AbstractTest extends AbstractTempDirTest {
49  
50      protected interface StreamWrapper<I extends InputStream> {
51          I wrap(InputStream inputStream) throws Exception;
52      }
53  
54      /**
55       * Deletes a file or directory. For a directory, delete it and all subdirectories.
56       *
57       * @param file a file or directory.
58       * @return whether deletion was successful
59       */
60      public static boolean forceDelete(final File file) {
61          try {
62              if (file != null && file.exists()) {
63                  FileUtils.forceDelete(file);
64              }
65              return true;
66          } catch (final IOException e) {
67              e.printStackTrace();
68              file.deleteOnExit();
69              return false;
70          }
71      }
72  
73      /**
74       * Deletes a file or directory. For a directory, delete it and all subdirectories.
75       *
76       * @param path a file or directory
77       * @return whether deletion was successful
78       */
79      public static boolean forceDelete(final Path path) {
80          return forceDelete(path != null ? path.toFile() : null);
81      }
82  
83      public static File getFile(final String path) throws IOException {
84          final URL url = AbstractTest.class.getClassLoader().getResource(path);
85          if (url == null) {
86              throw new FileNotFoundException("couldn't find " + path);
87          }
88          try {
89              return new File(url.toURI());
90          } catch (final URISyntaxException ex) {
91              throw new IOException(ex);
92          }
93      }
94  
95      public static Path getPath(final String path) throws IOException {
96          return getFile(path).toPath();
97      }
98  
99      public static InputStream newInputStream(final String path) throws IOException {
100         return Files.newInputStream(getPath(path));
101     }
102 
103     public static byte[] readAllBytes(final String path) throws IOException {
104         return Files.readAllBytes(getPath(path));
105     }
106 
107     @TempDir
108     protected File tempResultDir;
109 
110     /** Lists the content of the archive as originally created. */
111     protected List<String> archiveList;
112 
113     protected final ArchiveStreamFactory factory = ArchiveStreamFactory.DEFAULT;
114 
115     /**
116      * Add an entry to the archive, and keep track of the names in archiveList.
117      *
118      * @param outputStream
119      * @param fileName
120      * @param inputFile
121      * @throws IOException
122      * @throws FileNotFoundException
123      */
124     private <O extends ArchiveOutputStream<E>, E extends ArchiveEntry> void addArchiveEntry(final O outputStream, final String fileName, final File inputFile)
125             throws IOException, FileNotFoundException {
126         final E entry = outputStream.createArchiveEntry(inputFile, fileName);
127         outputStream.putArchiveEntry(entry);
128         Files.copy(inputFile.toPath(), outputStream);
129         outputStream.closeArchiveEntry();
130         archiveList.add(fileName);
131     }
132 
133     /**
134      * Checks that an archive input stream can be read, and that the file data matches file sizes.
135      *
136      * @param inputStream
137      * @param expected    list of expected entries or {@code null} if no check of names desired
138      * @throws Exception
139      */
140     protected void checkArchiveContent(final ArchiveInputStream<?> inputStream, final List<String> expected) throws Exception {
141         checkArchiveContent(inputStream, expected, true);
142     }
143 
144     /**
145      * Checks that an archive input stream can be read, and that the file data matches file sizes.
146      *
147      * @param inputStream
148      * @param expected    list of expected entries or {@code null} if no check of names desired
149      * @param cleanUp     Cleans up resources if true
150      * @return returns the created result file if cleanUp = false, or null otherwise
151      * @throws Exception
152      */
153     protected File checkArchiveContent(final ArchiveInputStream<?> inputStream, final List<String> expected, final boolean cleanUp) throws Exception {
154         final Path targetDir = createTempDirectory("dir-result");
155         final Path result = targetDir.resolve("result");
156         try {
157             ArchiveEntry entry;
158             while ((entry = inputStream.getNextEntry()) != null) {
159                 final Path outputFile = entry.resolveIn(result);
160                 long bytesCopied = 0;
161                 if (entry.isDirectory()) {
162                     Files.createDirectories(outputFile);
163                 } else {
164                     Files.createDirectories(outputFile.getParent());
165                     bytesCopied = Files.copy(inputStream, outputFile);
166                 }
167                 final long size = entry.getSize();
168                 if (size != ArchiveEntry.SIZE_UNKNOWN) {
169                     assertEquals(size, bytesCopied, "Entry.size should equal bytes read.");
170                 }
171 
172                 if (!Files.exists(outputFile)) {
173                     fail("Extraction failed: " + entry.getName());
174                 }
175                 if (expected != null && !expected.remove(getExpectedString(entry))) {
176                     fail("Unexpected entry: " + getExpectedString(entry));
177                 }
178             }
179             inputStream.close();
180             if (expected != null && !expected.isEmpty()) {
181                 fail(expected.size() + " missing entries: " + Arrays.toString(expected.toArray()));
182             }
183             if (expected != null) {
184                 assertEquals(0, expected.size());
185             }
186         } finally {
187             if (cleanUp) {
188                 forceDelete(targetDir);
189             }
190         }
191         return targetDir.toFile();
192     }
193 
194     /**
195      * Checks if an archive contains all expected files.
196      *
197      * @param archive  the archive to check
198      * @param expected a list with expected string file names
199      * @throws Exception
200      */
201     protected void checkArchiveContent(final File archive, final List<String> expected) throws Exception {
202         checkArchiveContent(archive.toPath(), expected);
203     }
204 
205     /**
206      * Checks if an archive contains all expected files.
207      *
208      * @param archive  the archive to check
209      * @param expected a list with expected string file names
210      * @throws Exception
211      */
212     protected void checkArchiveContent(final Path archive, final List<String> expected) throws Exception {
213         try (InputStream inputStream = Files.newInputStream(archive);
214                 ArchiveInputStream<?> archiveInputStream = factory.createArchiveInputStream(new BufferedInputStream(inputStream))) {
215             checkArchiveContent(archiveInputStream, expected);
216         }
217     }
218 
219     protected void closeQuietly(final Closeable closeable) {
220         IOUtils.closeQuietly(closeable);
221     }
222 
223     /**
224      * Creates an archive of text-based files in several directories. The archive name is the factory identifier for the archiver, for example zip, tar, cpio,
225      * jar, ar. The archive is created as a temp file.
226      *
227      * The archive contains the following files:
228      * <ul>
229      * <li>testdata/test1.xml</li>
230      * <li>testdata/test2.xml</li>
231      * <li>test/test3.xml</li>
232      * <li>bla/test4.xml</li>
233      * <li>bla/test5.xml</li>
234      * <li>bla/blubber/test6.xml</li>
235      * <li>test.txt</li>
236      * <li>something/bla</li>
237      * <li>test with spaces.txt</li>
238      * </ul>
239      *
240      * @param archiveName the identifier of this archive
241      * @return the newly created file
242      * @throws Exception in case something goes wrong
243      */
244     protected Path createArchive(final String archiveName) throws Exception {
245         final Path archivePath = createTempPath("test", "." + archiveName);
246         archiveList = new ArrayList<>();
247         try (OutputStream outputStream = Files.newOutputStream(archivePath);
248                 ArchiveOutputStream<ArchiveEntry> archiveOutputStream = factory.createArchiveOutputStream(archiveName, outputStream)) {
249             setLongFileMode(archiveOutputStream);
250             final File file1 = getFile("test1.xml");
251             final File file2 = getFile("test2.xml");
252             final File file3 = getFile("test3.xml");
253             final File file4 = getFile("test4.xml");
254             final File file5 = getFile("test.txt");
255             final File file6 = getFile("test with spaces.txt");
256 
257             addArchiveEntry(archiveOutputStream, "testdata/test1.xml", file1);
258             addArchiveEntry(archiveOutputStream, "testdata/test2.xml", file2);
259             addArchiveEntry(archiveOutputStream, "test/test3.xml", file3);
260             addArchiveEntry(archiveOutputStream, "bla/test4.xml", file4);
261             addArchiveEntry(archiveOutputStream, "bla/test5.xml", file4);
262             addArchiveEntry(archiveOutputStream, "bla/blubber/test6.xml", file4);
263             addArchiveEntry(archiveOutputStream, "test.txt", file5);
264             addArchiveEntry(archiveOutputStream, "something/bla", file6);
265             addArchiveEntry(archiveOutputStream, "test with spaces.txt", file6);
266 
267             archiveOutputStream.finish();
268             return archivePath;
269         }
270     }
271 
272     /**
273      * Create an empty archive.
274      *
275      * @param archiveName
276      * @return the archive File
277      * @throws Exception
278      */
279     protected Path createEmptyArchive(final String archiveName) throws Exception {
280         archiveList = new ArrayList<>();
281         final Path archivePath = createTempPath("empty", "." + archiveName);
282         try (OutputStream outputStream = Files.newOutputStream(archivePath);
283                 ArchiveOutputStream<?> archiveOutputStream = factory.createArchiveOutputStream(archiveName, outputStream)) {
284             archiveOutputStream.finish();
285         }
286         return archivePath;
287     }
288 
289     /**
290      * Create an archive with a single file "test1.xml".
291      *
292      * @param archiveName
293      * @return the archive File
294      * @throws Exception
295      */
296     protected Path createSingleEntryArchive(final String archiveName) throws Exception {
297         archiveList = new ArrayList<>();
298         final Path archivePath = createTempPath("empty", "." + archiveName);
299         try (OutputStream outputStream = Files.newOutputStream(archivePath);
300                 ArchiveOutputStream<?> archiveOutputStream = factory.createArchiveOutputStream(archiveName, outputStream)) {
301             // Use short file name so does not cause problems for ar
302             addArchiveEntry(archiveOutputStream, "test1.xml", getFile("test1.xml"));
303             archiveOutputStream.finish();
304         }
305         return archivePath;
306     }
307 
308     public Path createTempDirectory(final String prefix) throws IOException {
309         return Files.createTempDirectory(getTempDirPath(), prefix);
310     }
311 
312     /**
313      * Override this method to change what is to be compared in the List. For example, size + name instead of just name.
314      *
315      * @param entry
316      * @return returns the entry name
317      */
318     protected String getExpectedString(final ArchiveEntry entry) {
319         return entry.getName();
320     }
321 
322     protected void setLongFileMode(final ArchiveOutputStream<?> outputStream) {
323         if (outputStream instanceof ArArchiveOutputStream) {
324             ((ArArchiveOutputStream) outputStream).setLongFileMode(ArArchiveOutputStream.LONGFILE_BSD);
325         }
326     }
327 
328 }