/* * Copyright (c) 2011-2014 Jeppetto and Jonathan Thompson * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.iternine.jeppetto.testsupport; import java.io.File; import java.io.IOException; import java.io.FileOutputStream; import java.util.Random; import java.util.Set; import java.util.HashSet; import java.util.List; import java.util.ArrayList; import java.util.zip.ZipInputStream; import java.util.zip.ZipEntry; /** * This prvoides a convenient way to manage files for unit tests. * It allows for creating a per-test directory structure that can be used for temporary data as well as * load directory/file structures using zip archives. * * To use: * Create a FileSystemFixtureSupport. You can either specify a root directory or a name prefix to use * when creating the temporary directory. Your test can create new directories using <code>createDirectory()</code> * or load pre-built zip archives of directory structures into the temporary directory. In your test's * <code>@Before</code> method, you can cleanup the files/directories created during the test through the * <code>teardown()</code> method. You can also delete the temporary directory by calling the * <code>destroy()</code> method from an <code>@AfterClass</code> method. Once this is called, the fixture support * cannot be used. */ public class FileSystemFixtureSupport { //------------------------------------------------------------- // Variables - Private //------------------------------------------------------------- private File rootDir; private Set<File> fixtureFiles = new HashSet<File>(); //------------------------------------------------------------- // Constructors //------------------------------------------------------------- /** * Create a FileSystemFixtureSupport that will create a new temporary directory using the specified * name prefix. * @param prefix the prefix used when naming the temporary directory this will manage */ public FileSystemFixtureSupport(String prefix) { String tmpDir = System.getProperty("java.io.tmpdir"); rootDir = null; while(true) { rootDir = new File(tmpDir + File.separator + prefix + System.currentTimeMillis() + new Random().nextInt()); if (! rootDir.exists()) { createDirectoryOrThrow(rootDir); return; } } } /** * Create a FileSystemFixtureSupport that will manage the specified File as its temporary root directory. * When destroy() is invoked, this directory will be deleted. * @param rootDir the temporary root directory. This will be created if it does not already exist. */ public FileSystemFixtureSupport(File rootDir) { this.rootDir = rootDir; if (! rootDir.exists()) { createDirectoryOrThrow(rootDir); } } //------------------------------------------------------------- // Methods - Public //------------------------------------------------------------- /** * Creates a new directory that is relative to the temporary testing root. * @param path the relative path of the new directory. * @return the new directory */ public File createDirectory(String path) { verifyNotDestroyed(); File newDirectory = new File(rootDir + File.separator + path); fixtureFiles.add(newDirectory); return newDirectory; } /** * Loads a directory structure from a zip archive. The archive will be extracted at the testing root dierectory. * @param resourcesToLoad the names of zip archives in the classpath that should be loaded. * @throws IOException if we cannot unarchive the files */ public void loadFileFixtures(List<String> resourcesToLoad) throws IOException { verifyNotDestroyed(); for (String resource: resourcesToLoad) { loadTestFixtureFromClasspath(resource); } } /** * Deletes any directories or files that have been created by this class. * @throws IOException if we are unable to delete the data */ public void teardown() throws IOException { verifyNotDestroyed(); List<String> filesThatWouldnotDelete = new ArrayList<String>(); for (File fixtureFile: fixtureFiles) { if (fixtureFile.exists() && !deleteFile(fixtureFile)) { filesThatWouldnotDelete.add(fixtureFile.getAbsolutePath()); } } fixtureFiles.clear(); if (! filesThatWouldnotDelete.isEmpty()) { throw new IOException("Unable to delete the following files: " + filesThatWouldnotDelete); } } /** * Deletes all of the fixture files as well as the temporary root directory. This should only be invoked when the * this class is no longer going to be used. * @throws IOException if deletion fails */ public void destroy() throws IOException{ teardown(); if (! deleteFile(rootDir)) { throw new RuntimeException("Unable to delete contents of directory " + rootDir); } rootDir = null; } //------------------------------------------------------------- // Methods - Getter/Setter //------------------------------------------------------------- public File getTestingRoot() { verifyNotDestroyed(); return rootDir; } //------------------------------------------------------------- // Methods - Private //------------------------------------------------------------- private void createDirectoryOrThrow(File dir) { if (! dir.mkdirs()) { throw new RuntimeException("Unable to create the specified directory " + dir); } } private void loadTestFixtureFromClasspath(String resourceName) throws IOException { ZipInputStream zipInputStream = null; FileOutputStream fileOutStream = null; try { zipInputStream = new ZipInputStream(ClassLoader.getSystemResourceAsStream(resourceName)); ZipEntry nextEntry = null; while ((nextEntry = zipInputStream.getNextEntry()) != null) { String entryPath = nextEntry.getName(); File outFile = new File(rootDir.getAbsolutePath() + File.separator + entryPath); if(! outFile.getParentFile().exists()) { createDirectoryOrThrow(outFile.getParentFile()); } fileOutStream = new FileOutputStream(outFile); byte[] buffer = new byte[1024]; int read = 0; while ((read = zipInputStream.read(buffer, 0, buffer.length)) != -1) { fileOutStream.write(buffer, 0, read); } fileOutStream.close(); fileOutStream = null; fixtureFiles.add(outFile); } } finally { if (zipInputStream != null) { zipInputStream.close(); } if (fileOutStream != null) { fileOutStream.close(); } } } private boolean deleteFile(File file) { if( file.exists() ) { if (file.isDirectory()) { File[] files = file.listFiles(); for(int i=0; i<files.length; i++) { if(files[i].isDirectory()) { deleteFile(files[i]); } else { files[i].delete(); } } } } return(file.delete()); } private void verifyNotDestroyed() { if (rootDir == null) { throw new IllegalStateException("Fixture has already been destroyed."); } } }