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