/* * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org> * * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * - Neither the name of the Git Development Community nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.spearce.jgit.lib; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.util.ArrayList; import java.util.List; import junit.framework.TestCase; import org.spearce.jgit.util.JGitTestUtil; import org.spearce.jgit.util.SystemReader; /** * Base class for most JGit unit tests. * * Sets up a predefined test repository and has support for creating additional * repositories and destroying them when the tests are finished. * * A system property <em>jgit.junit.usemmap</em> defines whether memory mapping * is used. Memory mapping has an effect on the file system, in that memory * mapped files in java cannot be deleted as long as they mapped arrays have not * been reclaimed by the garbage collector. The programmer cannot control this * with precision, though hinting using <em>{@link java.lang.System#gc}</em> * often helps. */ public abstract class RepositoryTestCase extends TestCase { protected final File trashParent = new File("trash"); protected File trash; protected File trash_git; protected static final PersonIdent jauthor; protected static final PersonIdent jcommitter; static { jauthor = new PersonIdent("J. Author", "jauthor@example.com"); jcommitter = new PersonIdent("J. Committer", "jcommitter@example.com"); } protected boolean packedGitMMAP; /** * Configure JGit before setting up test repositories. */ protected void configure() { final WindowCacheConfig c = new WindowCacheConfig(); c.setPackedGitLimit(128 * WindowCacheConfig.KB); c.setPackedGitWindowSize(8 * WindowCacheConfig.KB); c.setPackedGitMMAP("true".equals(System.getProperty("jgit.junit.usemmap"))); c.setDeltaBaseCacheLimit(8 * WindowCacheConfig.KB); WindowCache.reconfigure(c); } /** * Utility method to delete a directory recursively. It is * also used internally. If a file or directory cannot be removed * it throws an AssertionFailure. * * @param dir */ protected void recursiveDelete(final File dir) { recursiveDelete(dir, false, getClass().getName() + "." + getName(), true); } protected static boolean recursiveDelete(final File dir, boolean silent, final String name, boolean failOnError) { assert !(silent && failOnError); if (!dir.exists()) return silent; final File[] ls = dir.listFiles(); if (ls != null) { for (int k = 0; k < ls.length; k++) { final File e = ls[k]; if (e.isDirectory()) { silent = recursiveDelete(e, silent, name, failOnError); } else { if (!e.delete()) { if (!silent) { reportDeleteFailure(name, failOnError, e); } silent = !failOnError; } } } } if (!dir.delete()) { if (!silent) { reportDeleteFailure(name, failOnError, dir); } silent = !failOnError; } return silent; } private static void reportDeleteFailure(final String name, boolean failOnError, final File e) { String severity; if (failOnError) severity = "Error"; else severity = "Warning"; String msg = severity + ": Failed to delete " + e; if (name != null) msg += " in " + name; if (failOnError) fail(msg); else System.out.println(msg); } protected static void copyFile(final File src, final File dst) throws IOException { final FileInputStream fis = new FileInputStream(src); try { final FileOutputStream fos = new FileOutputStream(dst); try { final byte[] buf = new byte[4096]; int r; while ((r = fis.read(buf)) > 0) { fos.write(buf, 0, r); } } finally { fos.close(); } } finally { fis.close(); } } protected File writeTrashFile(final String name, final String data) throws IOException { File tf = new File(trash, name); File tfp = tf.getParentFile(); if (!tfp.exists() && !tf.getParentFile().mkdirs()) throw new Error("Could not create directory " + tf.getParentFile()); final OutputStreamWriter fw = new OutputStreamWriter( new FileOutputStream(tf), "UTF-8"); try { fw.write(data); } finally { fw.close(); } return tf; } protected static void checkFile(File f, final String checkData) throws IOException { Reader r = new InputStreamReader(new FileInputStream(f), "ISO-8859-1"); try { char[] data = new char[(int) f.length()]; if (f.length() != r.read(data)) throw new IOException("Internal error reading file data from "+f); assertEquals(checkData, new String(data)); } finally { r.close(); } } protected Repository db; private static Thread shutdownhook; private static List<Runnable> shutDownCleanups = new ArrayList<Runnable>(); private static int testcount; private ArrayList<Repository> repositoriesToClose = new ArrayList<Repository>(); public void setUp() throws Exception { super.setUp(); configure(); final String name = getClass().getName() + "." + getName(); recursiveDelete(trashParent, true, name, false); // Cleanup old failed stuff trash = new File(trashParent,"trash"+System.currentTimeMillis()+"."+(testcount++)); trash_git = new File(trash, ".git").getCanonicalFile(); if (shutdownhook == null) { shutdownhook = new Thread() { @Override public void run() { // This may look superfluous, but is an extra attempt // to clean up. First GC to release as many resources // as possible and then try to clean up one test repo // at a time (to record problems) and finally to drop // the directory containing all test repositories. System.gc(); for (Runnable r : shutDownCleanups) r.run(); recursiveDelete(trashParent, false, null, false); } }; Runtime.getRuntime().addShutdownHook(shutdownhook); } final MockSystemReader mockSystemReader = new MockSystemReader(); mockSystemReader.userGitConfig = new FileBasedConfig(new File( trash_git, "usergitconfig")); SystemReader.setInstance(mockSystemReader); db = new Repository(trash_git); db.create(); final String[] packs = { "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f", "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371", "pack-9fb5b411fe6dfa89cc2e6b89d2bd8e5de02b5745", "pack-546ff360fe3488adb20860ce3436a2d6373d2796", "pack-cbdeda40019ae0e6e789088ea0f51f164f489d14", "pack-e6d07037cbcf13376308a0a995d1fa48f8f76aaa", "pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12" }; final File packDir = new File(db.getObjectsDirectory(), "pack"); for (int k = 0; k < packs.length; k++) { copyFile(JGitTestUtil.getTestResourceFile(packs[k] + ".pack"), new File(packDir, packs[k] + ".pack")); copyFile(JGitTestUtil.getTestResourceFile(packs[k] + ".idx"), new File(packDir, packs[k] + ".idx")); } copyFile(JGitTestUtil.getTestResourceFile("packed-refs"), new File(trash_git,"packed-refs")); } protected void tearDown() throws Exception { RepositoryCache.clear(); db.close(); for (Repository r : repositoriesToClose) r.close(); // Since memory mapping is controlled by the GC we need to // tell it this is a good time to clean up and unlock // memory mapped files. if (packedGitMMAP) System.gc(); final String name = getClass().getName() + "." + getName(); recursiveDelete(trash, false, name, true); for (Repository r : repositoriesToClose) recursiveDelete(r.getWorkDir(), false, name, true); repositoriesToClose.clear(); super.tearDown(); } /** * Helper for creating extra empty repos * * @return a new empty git repository for testing purposes * * @throws IOException */ protected Repository createNewEmptyRepo() throws IOException { return createNewEmptyRepo(false); } /** * Helper for creating extra empty repos * * @param bare if true, create a bare repository. * @return a new empty git repository for testing purposes * * @throws IOException */ protected Repository createNewEmptyRepo(boolean bare) throws IOException { final File newTestRepo = new File(trashParent, "new" + System.currentTimeMillis() + "." + (testcount++) + (bare ? "" : "/") + ".git").getCanonicalFile(); assertFalse(newTestRepo.exists()); final Repository newRepo = new Repository(newTestRepo); newRepo.create(); final String name = getClass().getName() + "." + getName(); shutDownCleanups.add(new Runnable() { public void run() { recursiveDelete(newTestRepo, false, name, false); } }); repositoriesToClose.add(newRepo); return newRepo; } protected void setupReflog(String logName, byte[] data) throws FileNotFoundException, IOException { File logfile = new File(db.getDirectory(), logName); if (!logfile.getParentFile().mkdirs() && !logfile.getParentFile().isDirectory()) { throw new IOException( "oops, cannot create the directory for the test reflog file" + logfile); } FileOutputStream fileOutputStream = new FileOutputStream(logfile); try { fileOutputStream.write(data); } finally { fileOutputStream.close(); } } }