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 setUp()57 public void setUp() throws Exception { 58 dir = mkdir("dir"); 59 resultDir = mkdir("dir-result"); 60 archive = null; 61 } 62 mkdir(final String name)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 getFile(final String path)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 tearDown()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 rmdir(final File f)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 */ tryHardToDelete(final File f)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 */ createArchive(final String archivename)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 */ addArchiveEntry(final ArchiveOutputStream out, final String filename, final File infile)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 */ createEmptyArchive(final String archivename)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 */ createSingleEntryArchive(final String archivename)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 */ checkArchiveContent(final File archive, final List<String> expected)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 */ checkArchiveContent(final ArchiveInputStream in, final List<String> expected)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 */ checkArchiveContent(final ArchiveInputStream in, final List<String> expected, final boolean cleanUp)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 */ getExpectedString(final ArchiveEntry entry)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 */ createTempDirAndFile()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 createTempDir()387 protected File createTempDir() throws IOException { 388 final File tmpDir = mkdir("testdir"); 389 tmpDir.deleteOnExit(); 390 return tmpDir; 391 } 392 closeQuietly(final Closeable closeable)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> { wrap(InputStream in)404 I wrap(InputStream in) throws Exception; 405 } 406 } 407