001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress;
020
021import java.io.BufferedInputStream;
022import java.io.Closeable;
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.FileNotFoundException;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.OutputStream;
030import java.net.URI;
031import java.net.URL;
032import java.util.ArrayList;
033import java.util.List;
034import java.util.Locale;
035
036import junit.framework.TestCase;
037
038import org.apache.commons.compress.archivers.ArchiveEntry;
039import org.apache.commons.compress.archivers.ArchiveInputStream;
040import org.apache.commons.compress.archivers.ArchiveOutputStream;
041import org.apache.commons.compress.archivers.ArchiveStreamFactory;
042import org.apache.commons.compress.utils.IOUtils;
043
044public abstract class AbstractTestCase extends TestCase {
045
046    protected File dir;
047    protected File resultDir;
048
049    private File archive; // used to delete the archive in tearDown
050    protected List<String> archiveList; // Lists the content of the archive as originally created
051
052    protected ArchiveStreamFactory factory = new ArchiveStreamFactory();
053
054    public AbstractTestCase() {
055        super();
056    }
057
058    public AbstractTestCase(String name) {
059        super(name);
060    }
061
062    @Override
063    protected void setUp() throws Exception {
064        dir = mkdir("dir");
065        resultDir = mkdir("dir-result");
066        archive = null;
067    }
068
069    public static File mkdir(String name) throws IOException {
070        File f = File.createTempFile(name, "");
071        f.delete();
072        f.mkdir();
073        return f;
074    }
075
076    public static File getFile(String path) throws IOException {
077        URL url = AbstractTestCase.class.getClassLoader().getResource(path);
078        if (url == null) {
079            throw new FileNotFoundException("couldn't find " + path);
080        }
081        URI uri = null;
082        try {
083            uri = url.toURI();
084        } catch (java.net.URISyntaxException ex) {
085//          throw new IOException(ex); // JDK 1.6+
086            IOException ioe = new IOException();
087            ioe.initCause(ex);
088            throw ioe;
089        }
090        return new File(uri);
091    }
092
093    @Override
094    protected void tearDown() throws Exception {
095        rmdir(dir);
096        rmdir(resultDir);
097        dir = resultDir = null;
098        if (!tryHardToDelete(archive)) {
099            // Note: this exception won't be shown if the test has already failed
100            throw new Exception("Could not delete "+archive.getPath());
101        }
102    }
103
104    public static void rmdir(File f) {
105        String[] s = f.list();
106        if (s != null) {
107            for (String element : s) {
108                final File file = new File(f, element);
109                if (file.isDirectory()){
110                    rmdir(file);
111                }
112                boolean ok = tryHardToDelete(file);
113                if (!ok && file.exists()){
114                    System.out.println("Failed to delete "+element+" in "+f.getPath());
115                }
116            }
117        }
118        tryHardToDelete(f); // safer to delete and check
119        if (f.exists()){
120            throw new Error("Failed to delete "+f.getPath());
121        }
122    }
123
124    private static final boolean ON_WINDOWS =
125        System.getProperty("os.name").toLowerCase(Locale.ENGLISH)
126        .indexOf("windows") > -1;
127
128    /**
129     * Accommodate Windows bug encountered in both Sun and IBM JDKs.
130     * Others possible. If the delete does not work, call System.gc(),
131     * wait a little and try again.
132     *
133     * @return whether deletion was successful
134     * @since Stolen from FileUtils in Ant 1.8.0
135     */
136    public static boolean tryHardToDelete(File f) {
137        if (f != null && f.exists() && !f.delete()) {
138            if (ON_WINDOWS) {
139                System.gc();
140            }
141            try {
142                Thread.sleep(10);
143            } catch (InterruptedException ex) {
144                // Ignore Exception
145            }
146            return f.delete();
147        }
148        return true;
149    }
150
151    /**
152     * Creates an archive of textbased files in several directories. The
153     * archivername is the factory identifier for the archiver, for example zip,
154     * tar, cpio, jar, ar. The archive is created as a temp file.
155     *
156     * The archive contains the following files:
157     * <ul>
158     * <li>testdata/test1.xml</li>
159     * <li>testdata/test2.xml</li>
160     * <li>test/test3.xml</li>
161     * <li>bla/test4.xml</li>
162     * <li>bla/test5.xml</li>
163     * <li>bla/blubber/test6.xml</li>
164     * <li>test.txt</li>
165     * <li>something/bla</li>
166     * <li>test with spaces.txt</li>
167     * </ul>
168     *
169     * @param archivename
170     *            the identifier of this archive
171     * @return the newly created file
172     * @throws Exception
173     *             in case something goes wrong
174     */
175    protected File createArchive(String archivename) throws Exception {
176        ArchiveOutputStream out = null;
177        OutputStream stream = null;
178        try {
179            archive = File.createTempFile("test", "." + archivename);
180            archive.deleteOnExit();
181            archiveList = new ArrayList<String>();
182
183            stream = new FileOutputStream(archive);
184            out = factory.createArchiveOutputStream(archivename, stream);
185
186            final File file1 = getFile("test1.xml");
187            final File file2 = getFile("test2.xml");
188            final File file3 = getFile("test3.xml");
189            final File file4 = getFile("test4.xml");
190            final File file5 = getFile("test.txt");
191            final File file6 = getFile("test with spaces.txt");
192
193            addArchiveEntry(out, "testdata/test1.xml", file1);
194            addArchiveEntry(out, "testdata/test2.xml", file2);
195            addArchiveEntry(out, "test/test3.xml", file3);
196            addArchiveEntry(out, "bla/test4.xml", file4);
197            addArchiveEntry(out, "bla/test5.xml", file4);
198            addArchiveEntry(out, "bla/blubber/test6.xml", file4);
199            addArchiveEntry(out, "test.txt", file5);
200            addArchiveEntry(out, "something/bla", file6);
201            addArchiveEntry(out, "test with spaces.txt", file6);
202
203            out.finish();
204            return archive;
205        } finally {
206            if (out != null) {
207                out.close();
208            } else if (stream != null) {
209                stream.close();
210            }
211        }
212    }
213
214    /**
215     * Add an entry to the archive, and keep track of the names in archiveList.
216     *
217     * @param out
218     * @param file1
219     * @throws IOException
220     * @throws FileNotFoundException
221     */
222    private void addArchiveEntry(ArchiveOutputStream out, String filename, final File infile)
223            throws IOException, FileNotFoundException {
224        ArchiveEntry entry = out.createArchiveEntry(infile, filename);
225        out.putArchiveEntry(entry);
226        IOUtils.copy(new FileInputStream(infile), out);
227        out.closeArchiveEntry();
228        archiveList.add(filename);
229    }
230
231    /**
232     * Create an empty archive.
233     * @param archivename
234     * @return the archive File
235     * @throws Exception
236     */
237    protected File createEmptyArchive(String archivename) throws Exception {
238        ArchiveOutputStream out = null;
239        OutputStream stream = null;
240        archiveList = new ArrayList<String>();
241        try {
242            archive = File.createTempFile("empty", "." + archivename);
243            archive.deleteOnExit();
244            stream = new FileOutputStream(archive);
245            out = factory.createArchiveOutputStream(archivename, stream);
246            out.finish();
247        } finally {
248            if (out != null) {
249                out.close();
250            } else if (stream != null) {
251                stream.close();
252            }
253        }
254        return archive;
255    }
256
257    /**
258     * Create an archive with a single file "test1.xml".
259     *
260     * @param archivename
261     * @return the archive File
262     * @throws Exception
263     */
264    protected File createSingleEntryArchive(String archivename) throws Exception {
265        ArchiveOutputStream out = null;
266        OutputStream stream = null;
267        archiveList = new ArrayList<String>();
268        try {
269            archive = File.createTempFile("empty", "." + archivename);
270            archive.deleteOnExit();
271            stream = new FileOutputStream(archive);
272            out = factory.createArchiveOutputStream(archivename, stream);
273            // Use short file name so does not cause problems for ar
274            addArchiveEntry(out, "test1.xml", getFile("test1.xml"));
275            out.finish();
276        } finally {
277            if (out != null) {
278                out.close();
279            } else if (stream != null) {
280                stream.close();
281            }
282        }
283        return archive;
284    }
285
286    /**
287     * Checks if an archive contains all expected files.
288     *
289     * @param archive
290     *            the archive to check
291     * @param expected
292     *            a list with expected string filenames
293     * @throws Exception
294     */
295    protected void checkArchiveContent(File archive, List<String> expected)
296            throws Exception {
297        final InputStream is = new FileInputStream(archive);
298        try {
299            final BufferedInputStream buf = new BufferedInputStream(is);
300            final ArchiveInputStream in = factory.createArchiveInputStream(buf);
301            this.checkArchiveContent(in, expected);
302        } finally {
303            is.close();
304        }
305    }
306
307    /**
308     * Checks that an archive input stream can be read, and that the file data matches file sizes.
309     *
310     * @param in
311     * @param expected list of expected entries or {@code null} if no check of names desired
312     * @throws Exception
313     */
314    protected void checkArchiveContent(ArchiveInputStream in, List<String> expected)
315            throws Exception {
316        checkArchiveContent(in, expected, true);
317    }
318
319    /**
320     * Checks that an archive input stream can be read, and that the file data matches file sizes.
321     *
322     * @param in
323     * @param expected list of expected entries or {@code null} if no check of names desired
324     * @param cleanUp Cleans up resources if true
325     * @return returns the created result file if cleanUp = false, or null otherwise
326     * @throws Exception
327     */
328    protected File checkArchiveContent(ArchiveInputStream in, List<String> expected, boolean cleanUp)
329            throws Exception {
330        File result = mkdir("dir-result");
331        result.deleteOnExit();
332
333        try {
334            ArchiveEntry entry = null;
335            while ((entry = in.getNextEntry()) != null) {
336                File outfile = new File(result.getCanonicalPath() + "/result/"
337                        + entry.getName());
338                long copied=0;
339                if (entry.isDirectory()){
340                    outfile.mkdirs();
341                } else {
342                    outfile.getParentFile().mkdirs();
343                    OutputStream out = new FileOutputStream(outfile);
344                    try {
345                        copied=IOUtils.copy(in, out);
346                    } finally {
347                        out.close();
348                    }
349                }
350                final long size = entry.getSize();
351                if (size != ArchiveEntry.SIZE_UNKNOWN) {
352                    assertEquals("Entry.size should equal bytes read.",size, copied);
353                }
354
355                if (!outfile.exists()) {
356                    fail("extraction failed: " + entry.getName());
357                }
358                if (expected != null && !expected.remove(getExpectedString(entry))) {
359                    fail("unexpected entry: " + getExpectedString(entry));
360                }
361            }
362            in.close();
363            if (expected != null && expected.size() > 0) {
364                for (String name : expected) {
365                    fail("Expected entry: " + name);
366                }
367            }
368            if (expected != null) {
369                assertEquals(0, expected.size());
370            }
371        } finally {
372            if (cleanUp) {
373                rmdir(result);
374            }
375        }
376        return result;
377    }
378
379    /**
380     * Override this method to change what is to be compared in the List.
381     * For example, size + name instead of just name.
382     *
383     * @param entry
384     * @return returns the entry name
385     */
386    protected String getExpectedString(ArchiveEntry entry) {
387        return entry.getName();
388    }
389
390    /**
391     * Creates a temporary directory and a temporary file inside that
392     * directory, returns both of them (the directory is the first
393     * element of the two element array).
394     */
395    protected File[] createTempDirAndFile() throws IOException {
396        File tmpDir = mkdir("testdir");
397        tmpDir.deleteOnExit();
398        File tmpFile = File.createTempFile("testfile", "", tmpDir);
399        tmpFile.deleteOnExit();
400        FileOutputStream fos = new FileOutputStream(tmpFile);
401        try {
402            fos.write(new byte[] {'f', 'o', 'o'});
403            return new File[] {tmpDir, tmpFile};
404        } finally {
405            fos.close();
406        }
407    }
408
409    protected void closeQuietly(Closeable closeable){
410        if (closeable != null) {
411            try {
412                closeable.close();
413            } catch (IOException ignored) {
414                // ignored
415            }
416        }
417    }
418}