Fix hardcoded use of target/trash in LocalDiskRepositoryTestCase

`pwd`/target is only valid in Maven Reactor builds where Maven
has moved into the project directory and created a target for
the build output. Most other build systems do not use "target"
and may not even perform a directory change between test suites.

Rewrite LocalDiskRepositoryTestCase's temporary directory code
to use the system specified location and create new unique names.
This prevents fixes between concurrently running tests and allows
the caller to specify the root using java.io.tmpdir.

Update the surefire command lines to use target within each project as
the system temporary directory during unit testing, preventing JGit's
own test suite from writing to /tmp or somewhere like C:\tmp.

Change-Id: I9e8431f6ddfc16fee89f677bcce67c99cfb56782
This commit is contained in:
Shawn Pearce 2013-05-08 17:34:47 -07:00
parent e27993f1f8
commit 36144e12d8
5 changed files with 94 additions and 75 deletions

View File

@ -94,7 +94,7 @@
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration> <configuration>
<argLine>-Xmx256m -Dfile.encoding=UTF-8</argLine> <argLine>-Xmx256m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -107,7 +107,7 @@
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration> <configuration>
<argLine>-Xmx256m -Dfile.encoding=UTF-8</argLine> <argLine>-Xmx256m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -89,10 +89,6 @@
* descriptors or address space for the test process. * descriptors or address space for the test process.
*/ */
public abstract class LocalDiskRepositoryTestCase { public abstract class LocalDiskRepositoryTestCase {
private static Thread shutdownHook;
private static int testCount;
private static final boolean useMMAP = "true".equals(System private static final boolean useMMAP = "true".equals(System
.getProperty("jgit.junit.usemmap")); .getProperty("jgit.junit.usemmap"));
@ -102,36 +98,20 @@ public abstract class LocalDiskRepositoryTestCase {
/** A fake (but stable) identity for committer fields in the test. */ /** A fake (but stable) identity for committer fields in the test. */
protected PersonIdent committer; protected PersonIdent committer;
private final File trash = new File(new File("target"), "trash");
private final List<Repository> toClose = new ArrayList<Repository>(); private final List<Repository> toClose = new ArrayList<Repository>();
private File tmp;
private MockSystemReader mockSystemReader; private MockSystemReader mockSystemReader;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
tmp = File.createTempFile("jgit_test_", "_tmp");
synchronized(this) { CleanupThread.deleteOnShutdown(tmp);
if (shutdownHook == null) { if (!tmp.delete() || !tmp.mkdir())
shutdownHook = new Thread() { throw new IOException("Cannot create " + tmp);
@Override
public void run() {
// On windows accidentally open files or memory
// mapped regions may prevent files from being deleted.
// Suggesting a GC increases the likelihood that our
// test repositories actually get removed after the
// tests, even in the case of failure.
System.gc();
recursiveDelete("SHUTDOWN", trash, false, false);
}
};
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
}
recursiveDelete(testId(), trash, true, false);
mockSystemReader = new MockSystemReader(); mockSystemReader = new MockSystemReader();
mockSystemReader.userGitConfig = new FileBasedConfig(new File(trash, mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp,
"usergitconfig"), FS.DETECTED); "usergitconfig"), FS.DETECTED);
ceilTestDirectories(getCeilings()); ceilTestDirectories(getCeilings());
SystemReader.setInstance(mockSystemReader); SystemReader.setInstance(mockSystemReader);
@ -152,9 +132,12 @@ public void run() {
c.install(); c.install();
} }
protected File getTemporaryDirectory() {
return tmp.getAbsoluteFile();
}
protected List<File> getCeilings() { protected List<File> getCeilings() {
return Collections.singletonList(trash.getParentFile().getAbsoluteFile()); return Collections.singletonList(getTemporaryDirectory());
} }
private void ceilTestDirectories(List<File> ceilings) { private void ceilTestDirectories(List<File> ceilings) {
@ -184,8 +167,10 @@ public void tearDown() throws Exception {
// //
if (useMMAP) if (useMMAP)
System.gc(); System.gc();
if (tmp != null)
recursiveDelete(testId(), trash, false, true); recursiveDelete(tmp, false, true);
if (tmp != null && !tmp.exists())
CleanupThread.removed(tmp);
} }
/** Increment the {@link #author} and {@link #committer} times. */ /** Increment the {@link #author} and {@link #committer} times. */
@ -206,11 +191,11 @@ protected void tick() {
* the recursively directory to delete, if present. * the recursively directory to delete, if present.
*/ */
protected void recursiveDelete(final File dir) { protected void recursiveDelete(final File dir) {
recursiveDelete(testId(), dir, false, true); recursiveDelete(dir, false, true);
} }
private static boolean recursiveDelete(final String testName, private static boolean recursiveDelete(final File dir,
final File dir, boolean silent, boolean failOnError) { boolean silent, boolean failOnError) {
assert !(silent && failOnError); assert !(silent && failOnError);
if (!dir.exists()) if (!dir.exists())
return silent; return silent;
@ -219,31 +204,24 @@ private static boolean recursiveDelete(final String testName,
for (int k = 0; k < ls.length; k++) { for (int k = 0; k < ls.length; k++) {
final File e = ls[k]; final File e = ls[k];
if (e.isDirectory()) if (e.isDirectory())
silent = recursiveDelete(testName, e, silent, failOnError); silent = recursiveDelete(e, silent, failOnError);
else if (!e.delete()) { else if (!e.delete()) {
if (!silent) if (!silent)
reportDeleteFailure(testName, failOnError, e); reportDeleteFailure(failOnError, e);
silent = !failOnError; silent = !failOnError;
} }
} }
if (!dir.delete()) { if (!dir.delete()) {
if (!silent) if (!silent)
reportDeleteFailure(testName, failOnError, dir); reportDeleteFailure(failOnError, dir);
silent = !failOnError; silent = !failOnError;
} }
return silent; return silent;
} }
private static void reportDeleteFailure(final String testName, private static void reportDeleteFailure(boolean failOnError, File e) {
final boolean failOnError, final File e) { String severity = failOnError ? "ERROR" : "WARNING";
final String severity; String msg = severity + ": Failed to delete " + e;
if (failOnError)
severity = "ERROR";
else
severity = "WARNING";
final String msg = severity + ": Failed to delete " + e + " in "
+ testName;
if (failOnError) if (failOnError)
fail(msg); fail(msg);
else else
@ -302,10 +280,6 @@ public void addRepoToClose(Repository r) {
toClose.add(r); toClose.add(r);
} }
private static String createUniqueTestFolderPrefix() {
return "test" + (System.currentTimeMillis() + "_" + (testCount++));
}
/** /**
* Creates a unique directory for a test * Creates a unique directory for a test
* *
@ -315,9 +289,7 @@ private static String createUniqueTestFolderPrefix() {
* @throws IOException * @throws IOException
*/ */
protected File createTempDirectory(String name) throws IOException { protected File createTempDirectory(String name) throws IOException {
String gitdirName = createUniqueTestFolderPrefix(); File directory = new File(createTempFile(), name);
File parent = new File(trash, gitdirName);
File directory = new File(parent, name);
FileUtils.mkdirs(directory); FileUtils.mkdirs(directory);
return directory.getCanonicalFile(); return directory.getCanonicalFile();
} }
@ -332,16 +304,31 @@ protected File createTempDirectory(String name) throws IOException {
* @throws IOException * @throws IOException
*/ */
protected File createUniqueTestGitDir(boolean bare) throws IOException { protected File createUniqueTestGitDir(boolean bare) throws IOException {
String gitdirName = createUniqueTestFolderPrefix(); String gitdirName = createTempFile().getPath();
if (!bare) if (!bare)
gitdirName += "/"; gitdirName += "/";
gitdirName += Constants.DOT_GIT; return new File(gitdirName + Constants.DOT_GIT);
File gitdir = new File(trash, gitdirName);
return gitdir.getCanonicalFile();
} }
/**
* Allocates a new unique file path that does not exist.
* <p>
* Unlike the standard {@code File.createTempFile} the returned path does
* not exist, but may be created by another thread in a race with the
* caller. Good luck.
* <p>
* This method is inherently unsafe due to a race condition between creating
* the name and the first use that reserves it.
*
* @return a unique path that does not exist.
* @throws IOException
*/
protected File createTempFile() throws IOException { protected File createTempFile() throws IOException {
return new File(trash, "tmp-" + UUID.randomUUID()).getCanonicalFile(); File p = File.createTempFile("tmp_", "", tmp);
if (!p.delete()) {
throw new IOException("Cannot obtain unique path " + tmp);
}
return p;
} }
/** /**
@ -399,7 +386,7 @@ private static void putPersonIdent(final Map<String, String> env,
* the file could not be written. * the file could not be written.
*/ */
protected File write(final String body) throws IOException { protected File write(final String body) throws IOException {
final File f = File.createTempFile("temp", "txt", trash); final File f = File.createTempFile("temp", "txt", tmp);
try { try {
write(f, body); write(f, body);
return f; return f;
@ -449,8 +436,41 @@ private static HashMap<String, String> cloneEnv() {
return new HashMap<String, String>(System.getenv()); return new HashMap<String, String>(System.getenv());
} }
private String testId() { private static final class CleanupThread extends Thread {
return getClass().getName() + "." + testCount; private static final CleanupThread me;
} static {
me = new CleanupThread();
Runtime.getRuntime().addShutdownHook(me);
}
static void deleteOnShutdown(File tmp) {
synchronized (me) {
me.toDelete.add(tmp);
}
}
static void removed(File tmp) {
synchronized (me) {
me.toDelete.remove(tmp);
}
}
private final List<File> toDelete = new ArrayList<File>();
@Override
public void run() {
// On windows accidentally open files or memory
// mapped regions may prevent files from being deleted.
// Suggesting a GC increases the likelihood that our
// test repositories actually get removed after the
// tests, even in the case of failure.
System.gc();
synchronized (this) {
boolean silent = false;
boolean failOnError = false;
for (File tmp : toDelete)
recursiveDelete(tmp, silent, failOnError);
}
}
}
} }

View File

@ -136,7 +136,7 @@
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration> <configuration>
<argLine>-Xmx256m -Dfile.encoding=UTF-8</argLine> <argLine>-Xmx256m -Dfile.encoding=UTF-8 -Djava.io.tmpdir=${project.build.directory}</argLine>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -187,16 +187,15 @@ public void testExceptionThrown_BareRepoGetIndexFile() throws Exception {
} }
} }
private static File getFile(String... pathComponents) throws IOException { private File getFile(String... pathComponents) throws IOException {
String rootPath = new File(new File("target"), "trash").getPath(); File dir = getTemporaryDirectory();
for (String pathComponent : pathComponents) for (String pathComponent : pathComponents)
rootPath = rootPath + File.separatorChar + pathComponent; dir = new File(dir, pathComponent);
File result = new File(rootPath); FileUtils.mkdirs(dir, true);
FileUtils.mkdirs(result, true); return dir;
return result;
} }
private static void setBare(File gitDir, boolean bare) throws IOException, private void setBare(File gitDir, boolean bare) throws IOException,
ConfigInvalidException { ConfigInvalidException {
FileBasedConfig cfg = configFor(gitDir); FileBasedConfig cfg = configFor(gitDir);
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
@ -204,7 +203,7 @@ private static void setBare(File gitDir, boolean bare) throws IOException,
cfg.save(); cfg.save();
} }
private static void setWorkTree(File gitDir, File workTree) private void setWorkTree(File gitDir, File workTree)
throws IOException, throws IOException,
ConfigInvalidException { ConfigInvalidException {
String path = workTree.getAbsolutePath(); String path = workTree.getAbsolutePath();
@ -214,7 +213,7 @@ private static void setWorkTree(File gitDir, File workTree)
cfg.save(); cfg.save();
} }
private static FileBasedConfig configFor(File gitDir) throws IOException, private FileBasedConfig configFor(File gitDir) throws IOException,
ConfigInvalidException { ConfigInvalidException {
File configPath = new File(gitDir, Constants.CONFIG); File configPath = new File(gitDir, Constants.CONFIG);
FileBasedConfig cfg = new FileBasedConfig(configPath, FS.DETECTED); FileBasedConfig cfg = new FileBasedConfig(configPath, FS.DETECTED);
@ -222,14 +221,14 @@ private static FileBasedConfig configFor(File gitDir) throws IOException,
return cfg; return cfg;
} }
private static void assertGitdirPath(Repository repo, String... expected) private void assertGitdirPath(Repository repo, String... expected)
throws IOException { throws IOException {
File exp = getFile(expected).getCanonicalFile(); File exp = getFile(expected).getCanonicalFile();
File act = repo.getDirectory().getCanonicalFile(); File act = repo.getDirectory().getCanonicalFile();
assertEquals("Wrong Git Directory", exp, act); assertEquals("Wrong Git Directory", exp, act);
} }
private static void assertWorkdirPath(Repository repo, String... expected) private void assertWorkdirPath(Repository repo, String... expected)
throws IOException { throws IOException {
File exp = getFile(expected).getCanonicalFile(); File exp = getFile(expected).getCanonicalFile();
File act = repo.getWorkTree().getCanonicalFile(); File act = repo.getWorkTree().getCanonicalFile();