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.Assert.*;
22  import java.io.BufferedInputStream;
23  import java.io.Closeable;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.net.URI;
32  import java.net.URL;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.List;
36  import java.util.Locale;
37  
38  import org.apache.commons.compress.archivers.ArchiveEntry;
39  import org.apache.commons.compress.archivers.ArchiveInputStream;
40  import org.apache.commons.compress.archivers.ArchiveOutputStream;
41  import org.apache.commons.compress.archivers.ArchiveStreamFactory;
42  import org.apache.commons.compress.utils.IOUtils;
43  import org.junit.After;
44  import org.junit.Before;
45  
46  public abstract class AbstractTestCase {
47  
48      protected File dir;
49      protected File resultDir;
50  
51      private File archive; // used to delete the archive in tearDown
52      protected List<String> archiveList; // Lists the content of the archive as originally created
53  
54      protected ArchiveStreamFactory factory = new ArchiveStreamFactory();
55  
56      @Before
57      public void setUp() throws Exception {
58          dir = mkdir("dir");
59          resultDir = mkdir("dir-result");
60          archive = null;
61      }
62  
63      public static File mkdir(final String name) throws IOException {
64          final File f = File.createTempFile(name, "");
65          f.delete();
66          f.mkdir();
67          return f;
68      }
69  
70      public static File getFile(final String path) throws IOException {
71          final URL url = AbstractTestCase.class.getClassLoader().getResource(path);
72          if (url == null) {
73              throw new FileNotFoundException("couldn't find " + path);
74          }
75          URI uri = null;
76          try {
77              uri = url.toURI();
78          } catch (final java.net.URISyntaxException ex) {
79              throw new IOException(ex);
80          }
81          return new File(uri);
82      }
83  
84      @After
85      public void tearDown() throws Exception {
86          rmdir(dir);
87          rmdir(resultDir);
88          dir = resultDir = null;
89          if (!tryHardToDelete(archive)) {
90              // Note: this exception won't be shown if the test has already failed
91              throw new Exception("Could not delete "+archive.getPath());
92          }
93      }
94  
95      public static void rmdir(final File f) {
96          final String[] s = f.list();
97          if (s != null) {
98              for (final String element : s) {
99                  final File file = new File(f, element);
100                 if (file.isDirectory()){
101                     rmdir(file);
102                 }
103                 final boolean ok = tryHardToDelete(file);
104                 if (!ok && file.exists()){
105                     System.out.println("Failed to delete "+element+" in "+f.getPath());
106                 }
107             }
108         }
109         tryHardToDelete(f); // safer to delete and check
110         if (f.exists()){
111             throw new Error("Failed to delete "+f.getPath());
112         }
113     }
114 
115     private static final boolean ON_WINDOWS =
116             System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows");
117 
118     /**
119      * Accommodate Windows bug encountered in both Sun and IBM JDKs.
120      * Others possible. If the delete does not work, call System.gc(),
121      * wait a little and try again.
122      *
123      * @return whether deletion was successful
124      * @since Stolen from FileUtils in Ant 1.8.0
125      */
126     public static boolean tryHardToDelete(final File f) {
127         if (f != null && f.exists() && !f.delete()) {
128             if (ON_WINDOWS) {
129                 System.gc();
130             }
131             try {
132                 Thread.sleep(10);
133             } catch (final InterruptedException ex) {
134                 // Ignore Exception
135             }
136             return f.delete();
137         }
138         return true;
139     }
140 
141     /**
142      * Creates an archive of textbased files in several directories. The
143      * archivername is the factory identifier for the archiver, for example zip,
144      * tar, cpio, jar, ar. The archive is created as a temp file.
145      *
146      * The archive contains the following files:
147      * <ul>
148      * <li>testdata/test1.xml</li>
149      * <li>testdata/test2.xml</li>
150      * <li>test/test3.xml</li>
151      * <li>bla/test4.xml</li>
152      * <li>bla/test5.xml</li>
153      * <li>bla/blubber/test6.xml</li>
154      * <li>test.txt</li>
155      * <li>something/bla</li>
156      * <li>test with spaces.txt</li>
157      * </ul>
158      *
159      * @param archivename
160      *            the identifier of this archive
161      * @return the newly created file
162      * @throws Exception
163      *             in case something goes wrong
164      */
165     protected File createArchive(final String archivename) throws Exception {
166         ArchiveOutputStream out = null;
167         OutputStream stream = null;
168         try {
169             archive = File.createTempFile("test", "." + archivename);
170             archive.deleteOnExit();
171             archiveList = new ArrayList<>();
172 
173             stream = new FileOutputStream(archive);
174             out = factory.createArchiveOutputStream(archivename, stream);
175 
176             final File file1 = getFile("test1.xml");
177             final File file2 = getFile("test2.xml");
178             final File file3 = getFile("test3.xml");
179             final File file4 = getFile("test4.xml");
180             final File file5 = getFile("test.txt");
181             final File file6 = getFile("test with spaces.txt");
182 
183             addArchiveEntry(out, "testdata/test1.xml", file1);
184             addArchiveEntry(out, "testdata/test2.xml", file2);
185             addArchiveEntry(out, "test/test3.xml", file3);
186             addArchiveEntry(out, "bla/test4.xml", file4);
187             addArchiveEntry(out, "bla/test5.xml", file4);
188             addArchiveEntry(out, "bla/blubber/test6.xml", file4);
189             addArchiveEntry(out, "test.txt", file5);
190             addArchiveEntry(out, "something/bla", file6);
191             addArchiveEntry(out, "test with spaces.txt", file6);
192 
193             out.finish();
194             return archive;
195         } finally {
196             if (out != null) {
197                 out.close();
198             } else if (stream != null) {
199                 stream.close();
200             }
201         }
202     }
203 
204     /**
205      * Add an entry to the archive, and keep track of the names in archiveList.
206      *
207      * @param out
208      * @param file1
209      * @throws IOException
210      * @throws FileNotFoundException
211      */
212     private void addArchiveEntry(final ArchiveOutputStream out, final String filename, final File infile)
213             throws IOException, FileNotFoundException {
214         final ArchiveEntry entry = out.createArchiveEntry(infile, filename);
215         out.putArchiveEntry(entry);
216         IOUtils.copy(new FileInputStream(infile), out);
217         out.closeArchiveEntry();
218         archiveList.add(filename);
219     }
220 
221     /**
222      * Create an empty archive.
223      * @param archivename
224      * @return the archive File
225      * @throws Exception
226      */
227     protected File createEmptyArchive(final String archivename) throws Exception {
228         ArchiveOutputStream out = null;
229         OutputStream stream = null;
230         archiveList = new ArrayList<>();
231         try {
232             archive = File.createTempFile("empty", "." + archivename);
233             archive.deleteOnExit();
234             stream = new FileOutputStream(archive);
235             out = factory.createArchiveOutputStream(archivename, stream);
236             out.finish();
237         } finally {
238             if (out != null) {
239                 out.close();
240             } else if (stream != null) {
241                 stream.close();
242             }
243         }
244         return archive;
245     }
246 
247     /**
248      * Create an archive with a single file "test1.xml".
249      *
250      * @param archivename
251      * @return the archive File
252      * @throws Exception
253      */
254     protected File createSingleEntryArchive(final String archivename) throws Exception {
255         ArchiveOutputStream out = null;
256         OutputStream stream = null;
257         archiveList = new ArrayList<>();
258         try {
259             archive = File.createTempFile("empty", "." + archivename);
260             archive.deleteOnExit();
261             stream = new FileOutputStream(archive);
262             out = factory.createArchiveOutputStream(archivename, stream);
263             // Use short file name so does not cause problems for ar
264             addArchiveEntry(out, "test1.xml", getFile("test1.xml"));
265             out.finish();
266         } finally {
267             if (out != null) {
268                 out.close();
269             } else if (stream != null) {
270                 stream.close();
271             }
272         }
273         return archive;
274     }
275 
276     /**
277      * Checks if an archive contains all expected files.
278      *
279      * @param archive
280      *            the archive to check
281      * @param expected
282      *            a list with expected string filenames
283      * @throws Exception
284      */
285     protected void checkArchiveContent(final File archive, final List<String> expected)
286             throws Exception {
287         try (InputStream is = new FileInputStream(archive)) {
288             final BufferedInputStream buf = new BufferedInputStream(is);
289             final ArchiveInputStream in = factory.createArchiveInputStream(buf);
290             this.checkArchiveContent(in, expected);
291         }
292     }
293 
294     /**
295      * Checks that an archive input stream can be read, and that the file data matches file sizes.
296      *
297      * @param in
298      * @param expected list of expected entries or {@code null} if no check of names desired
299      * @throws Exception
300      */
301     protected void checkArchiveContent(final ArchiveInputStream in, final List<String> expected)
302             throws Exception {
303         checkArchiveContent(in, expected, true);
304     }
305 
306     /**
307      * Checks that an archive input stream can be read, and that the file data matches file sizes.
308      *
309      * @param in
310      * @param expected list of expected entries or {@code null} if no check of names desired
311      * @param cleanUp Cleans up resources if true
312      * @return returns the created result file if cleanUp = false, or null otherwise
313      * @throws Exception
314      */
315     protected File checkArchiveContent(final ArchiveInputStream in, final List<String> expected, final boolean cleanUp)
316             throws Exception {
317         final File result = mkdir("dir-result");
318         result.deleteOnExit();
319 
320         try {
321             ArchiveEntry entry = null;
322             while ((entry = in.getNextEntry()) != null) {
323                 final File outfile = new File(result.getCanonicalPath() + "/result/"
324                         + entry.getName());
325                 long copied=0;
326                 if (entry.isDirectory()){
327                     outfile.mkdirs();
328                 } else {
329                     outfile.getParentFile().mkdirs();
330                     try (OutputStream out = new FileOutputStream(outfile)) {
331                         copied = IOUtils.copy(in, out);
332                     }
333                 }
334                 final long size = entry.getSize();
335                 if (size != ArchiveEntry.SIZE_UNKNOWN) {
336                     assertEquals("Entry.size should equal bytes read.",size, copied);
337                 }
338 
339                 if (!outfile.exists()) {
340                     fail("extraction failed: " + entry.getName());
341                 }
342                 if (expected != null && !expected.remove(getExpectedString(entry))) {
343                     fail("unexpected entry: " + getExpectedString(entry));
344                 }
345             }
346             in.close();
347             if (expected != null && expected.size() > 0) {
348                 fail(expected.size() + " missing entries: " + Arrays.toString(expected.toArray()));
349             }
350             if (expected != null) {
351                 assertEquals(0, expected.size());
352             }
353         } finally {
354             if (cleanUp) {
355                 rmdir(result);
356             }
357         }
358         return result;
359     }
360 
361     /**
362      * Override this method to change what is to be compared in the List.
363      * For example, size + name instead of just name.
364      *
365      * @param entry
366      * @return returns the entry name
367      */
368     protected String getExpectedString(final ArchiveEntry entry) {
369         return entry.getName();
370     }
371 
372     /**
373      * Creates a temporary directory and a temporary file inside that
374      * directory, returns both of them (the directory is the first
375      * element of the two element array).
376      */
377     protected File[] createTempDirAndFile() throws IOException {
378         final File tmpDir = createTempDir();
379         final File tmpFile = File.createTempFile("testfile", "", tmpDir);
380         tmpFile.deleteOnExit();
381         try (FileOutputStream fos = new FileOutputStream(tmpFile)) {
382             fos.write(new byte[] { 'f', 'o', 'o' });
383             return new File[] { tmpDir, tmpFile };
384         }
385     }
386 
387     protected File createTempDir() throws IOException {
388         final File tmpDir = mkdir("testdir");
389         tmpDir.deleteOnExit();
390         return tmpDir;
391     }
392 
393     protected void closeQuietly(final Closeable closeable){
394         if (closeable != null) {
395             try {
396                 closeable.close();
397             } catch (final IOException ignored) {
398                 // ignored
399             }
400         }
401     }
402 
403     protected static interface StreamWrapper<I extends InputStream> {
404         I wrap(InputStream in) throws Exception;
405     }
406 }