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<String>();
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<String>();
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<String>();
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         final InputStream is = new FileInputStream(archive);
288         try {
289             final BufferedInputStream buf = new BufferedInputStream(is);
290             final ArchiveInputStream in = factory.createArchiveInputStream(buf);
291             this.checkArchiveContent(in, expected);
292         } finally {
293             is.close();
294         }
295     }
296 
297     /**
298      * Checks that an archive input stream can be read, and that the file data matches file sizes.
299      *
300      * @param in
301      * @param expected list of expected entries or {@code null} if no check of names desired
302      * @throws Exception
303      */
304     protected void checkArchiveContent(final ArchiveInputStream in, final List<String> expected)
305             throws Exception {
306         checkArchiveContent(in, expected, true);
307     }
308 
309     /**
310      * Checks that an archive input stream can be read, and that the file data matches file sizes.
311      *
312      * @param in
313      * @param expected list of expected entries or {@code null} if no check of names desired
314      * @param cleanUp Cleans up resources if true
315      * @return returns the created result file if cleanUp = false, or null otherwise
316      * @throws Exception
317      */
318     protected File checkArchiveContent(final ArchiveInputStream in, final List<String> expected, final boolean cleanUp)
319             throws Exception {
320         final File result = mkdir("dir-result");
321         result.deleteOnExit();
322 
323         try {
324             ArchiveEntry entry = null;
325             while ((entry = in.getNextEntry()) != null) {
326                 final File outfile = new File(result.getCanonicalPath() + "/result/"
327                         + entry.getName());
328                 long copied=0;
329                 if (entry.isDirectory()){
330                     outfile.mkdirs();
331                 } else {
332                     outfile.getParentFile().mkdirs();
333                     final OutputStream out = new FileOutputStream(outfile);
334                     try {
335                         copied=IOUtils.copy(in, out);
336                     } finally {
337                         out.close();
338                     }
339                 }
340                 final long size = entry.getSize();
341                 if (size != ArchiveEntry.SIZE_UNKNOWN) {
342                     assertEquals("Entry.size should equal bytes read.",size, copied);
343                 }
344 
345                 if (!outfile.exists()) {
346                     fail("extraction failed: " + entry.getName());
347                 }
348                 if (expected != null && !expected.remove(getExpectedString(entry))) {
349                     fail("unexpected entry: " + getExpectedString(entry));
350                 }
351             }
352             in.close();
353             if (expected != null && expected.size() > 0) {
354                 fail(expected.size() + " missing entries: " + Arrays.toString(expected.toArray()));
355             }
356             if (expected != null) {
357                 assertEquals(0, expected.size());
358             }
359         } finally {
360             if (cleanUp) {
361                 rmdir(result);
362             }
363         }
364         return result;
365     }
366 
367     /**
368      * Override this method to change what is to be compared in the List.
369      * For example, size + name instead of just name.
370      *
371      * @param entry
372      * @return returns the entry name
373      */
374     protected String getExpectedString(final ArchiveEntry entry) {
375         return entry.getName();
376     }
377 
378     /**
379      * Creates a temporary directory and a temporary file inside that
380      * directory, returns both of them (the directory is the first
381      * element of the two element array).
382      */
383     protected File[] createTempDirAndFile() throws IOException {
384         final File tmpDir = createTempDir();
385         final File tmpFile = File.createTempFile("testfile", "", tmpDir);
386         tmpFile.deleteOnExit();
387         final FileOutputStream fos = new FileOutputStream(tmpFile);
388         try {
389             fos.write(new byte[] {'f', 'o', 'o'});
390             return new File[] {tmpDir, tmpFile};
391         } finally {
392             fos.close();
393         }
394     }
395 
396     protected File createTempDir() throws IOException {
397         final File tmpDir = mkdir("testdir");
398         tmpDir.deleteOnExit();
399         return tmpDir;
400     }
401 
402     protected void closeQuietly(final Closeable closeable){
403         if (closeable != null) {
404             try {
405                 closeable.close();
406             } catch (final IOException ignored) {
407                 // ignored
408             }
409         }
410     }
411 
412     protected static interface StreamWrapper<I extends InputStream> {
413         I wrap(InputStream in) throws Exception;
414     }
415 }