From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
Ok, so here is an attempt to improve the ability of the JGit's unit
tests to delete temporary repositories. This has probably been seen
by many, but Jonas Fonseca raised the issue.
The background is that on Windows you cannot delete files that are
open and mmapped files are open until they get unmapped, which in
Java is beyond explicit programmer control. You can only free the
resources and pray that the GC does the work. Fortunately it usually
does. It turned out our testcases weren't even trying to clean up
properly.
-- robin
Robin Rosenberg (4):
Make the cleanup less verbose when it fails to delete temporary
stuff.
Add shutdown hooks to try to clean up after unit tests anyway
Cleanup malformed test cases
Automatically clean up any repositories created by the test cases
.../tst/org/spearce/jgit/lib/PackWriterTest.java | 3 +
.../org/spearce/jgit/lib/RepositoryTestCase.java | 82 +++++++++++++++++---
2 files changed, 73 insertions(+), 12 deletions(-)
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:42
Robin Rosenberg [off-list ref] wrote:
Ok, so here is an attempt to improve the ability of the JGit's unit
tests to delete temporary repositories. This has probably been seen
by many, but Jonas Fonseca raised the issue.
Hmpph. This takes 19 seconds to run the suite, where it used to be
only 2 seconds on the same system. The slower run isn't something
I'm too happy about, actually I'd like to make the run even faster
than 2 seconds.
The background is that on Windows you cannot delete files that are
open and mmapped files are open until they get unmapped, which in
Java is beyond explicit programmer control. You can only free the
resources and pray that the GC does the work. Fortunately it usually
does. It turned out our testcases weren't even trying to clean up
properly.
If the issue is mmap'd files, why don't we instead disable mmap
on Windows during JUnit tests, and use the non-mmap variant of
pack access? At least do that for the bulk of the tests, and
then have a single test case which tests the mmap code path but
has careful System.gc calls in place to try and ensure we can
actually clean up the temporary files.
Another option is to refactor the Repository class a little so we
can replace local filesystem IO with something else, like say an
in-core repository. E.g. a pack file and/or pack index stored in
a byte[], and refs stored in a HashMap. We'd still need a couple
of tests to verify local disk IO, but many of the tests can be
validated against such a pure in-memory Repository concept.
I'd actually like to get that Repository refactoring done soon,
someone else was asking about it for the RefDatabase (to store the
refs in a SQL database so JGit ties into JTA) but I may also want
it for Gerrit 2 - I'm looking at doing something that would put
200,000 refs per year into a repository. That's so large that
most operations can't afford to scan the entire ref database,
and it really cannot be loose. ;-)
Refactoring repository is a fair chunk of work, disabling the mmap
feature under Windows in JUnit may be easier. Hmm, according to
WindowCache's <clinit> its default by false. Why is it enabling
on Windows? The only code that calls WindowCache.reconfigure()
is in the Eclipse plugin, so pure JGit unit tests shouldn't be
turning on mmap code *at all*.
Which also points out a gap in our tests. Nothing new, we have
lots of gaps. *sigh*
--
Shawn.
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
torsdag 27 november 2008 22:49:16 skrev Shawn O. Pearce:
Robin Rosenberg [off-list ref] wrote:
quoted
Ok, so here is an attempt to improve the ability of the JGit's unit
tests to delete temporary repositories. This has probably been seen
by many, but Jonas Fonseca raised the issue.
Hmpph. This takes 19 seconds to run the suite, where it used to be
only 2 seconds on the same system. The slower run isn't something
I'm too happy about, actually I'd like to make the run even faster
than 2 seconds.
So maybe we should clean up less. Every new test repo we create has
a new name so we could do without cleaning up so much. The cleanup
however is a verification that we close (and can close) our resources,
though it only works on Windows :/ On unix we could spawn lsof but that
is really really slow.
If the issue is mmap'd files, why don't we instead disable mmap
on Windows during JUnit tests, and use the non-mmap variant of
pack access? At least do that for the bulk of the tests, and
then have a single test case which tests the mmap code path but
has careful System.gc calls in place to try and ensure we can
actually clean up the temporary files.
We would then need some other really slow test to play rough with
memory mapping and gc. As I mentioned above it is actually about
closing resources in general, mmapped files being an especially
nasty case.
I'd actually like to get that Repository refactoring done soon,
someone else was asking about it for the RefDatabase (to store the
refs in a SQL database so JGit ties into JTA) but I may also want
it for Gerrit 2 - I'm looking at doing something that would put
200,000 refs per year into a repository. That's so large that
most operations can't afford to scan the entire ref database,
and it really cannot be loose. ;-)
Would be cool, but having that diff engine is more important to me.
Refactoring repository is a fair chunk of work, disabling the mmap
feature under Windows in JUnit may be easier. Hmm, according to
WindowCache's <clinit> its default by false. Why is it enabling
on Windows? The only code that calls WindowCache.reconfigure()
is in the Eclipse plugin, so pure JGit unit tests shouldn't be
turning on mmap code *at all*.
Which also points out a gap in our tests. Nothing new, we have
lots of gaps. *sigh*
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
torsdag 27 november 2008 22:49:16 skrev Shawn O. Pearce:
Robin Rosenberg [off-list ref] wrote:
quoted
Ok, so here is an attempt to improve the ability of the JGit's unit
tests to delete temporary repositories. This has probably been seen
by many, but Jonas Fonseca raised the issue.
Hmpph. This takes 19 seconds to run the suite, where it used to be
only 2 seconds on the same system. The slower run isn't something
I'm too happy about, actually I'd like to make the run even faster
than 2 seconds.
Ok, that's the GC calls, I added. I'll post a completely new set of patches
disabling mmap by default, overridable using system properties.
-- robin
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
A completele reworked set of patches, including fixing a couple
more forgot-to-close bugs and Shawns suggestion that we disable
memory mapping in junit tests by default.
-- robin
Robin Rosenberg (8):
Drop unneeded code in unit tests
Cleanup malformed test cases
Turn off memory mapping in JGit unit tests by default
Add a counter to make sure the test repo name is unique
Make the cleanup less verbose when it fails to delete temporary
stuff.
Cleanup after each test.
Close files opened by unit testing framework
Hard failure on unit test cleanups if they fail.
.../tst/org/spearce/jgit/lib/PackWriterTest.java | 3 +
.../org/spearce/jgit/lib/RepositoryTestCase.java | 152 +++++++++++++++++---
.../tst/org/spearce/jgit/lib/T0007_Index.java | 10 +-
3 files changed, 139 insertions(+), 26 deletions(-)
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
System.currentTimeMillis() does not have the granularity
necessary to guarantee uniqueness. We keep it to make sure we
have unique names between different runs, but add a counter to
make it unique within the execution of a test suite.
Signed-off-by: Robin Rosenberg <redacted>
---
.../org/spearce/jgit/lib/RepositoryTestCase.java | 6 ++++--
1 files changed, 4 insertions(+), 2 deletions(-)
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
A system property named jgit.junit.usemmmap can be set to true to enable
memory mapping during unit testing.
The protected method configure can be overridden to do things
like configuring the JGit engine.
Signed-off-by: Robin Rosenberg <redacted>
---
.../org/spearce/jgit/lib/RepositoryTestCase.java | 30 ++++++++++++++++++++
1 files changed, 30 insertions(+), 0 deletions(-)
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
This only has an effect on Windows that locks open files, but
is a nice test that we actually clean up.
Signed-off-by: Robin Rosenberg <redacted>
---
.../org/spearce/jgit/lib/RepositoryTestCase.java | 52 ++++++++++++--------
1 files changed, 32 insertions(+), 20 deletions(-)
@@ -110,32 +113,42 @@ protected static boolean recursiveDelete(final File dir, boolean silent,for(intk=0;k<ls.length;k++){finalFilee=ls[k];if(e.isDirectory()){-silent=recursiveDelete(e,silent,name);+silent=recursiveDelete(e,silent,name,failOnError);}else{if(!e.delete()){if(!silent){-Stringmsg="Warning: Failed to delete "+e;-if(name!=null)-msg+=" in "+name;-System.out.println(msg);+reportDeleteFailure(name,failOnError,e);}-silent=true;+silent=!failOnError;}}}}if(!dir.delete()){if(!silent){-Stringmsg="Warning: Failed to delete "+dir;-if(name!=null)-msg+=" in "+name;-System.out.println(msg);+reportDeleteFailure(name,failOnError,dir);}-silent=true;+silent=!failOnError;}returnsilent;}+privatestaticvoidreportDeleteFailure(finalStringname,+booleanfailOnError,finalFilee){+Stringseverity;+if(failOnError)+severity="Error";+else+severity="Warning";+Stringmsg=severity+": Failed to delete "+e;+if(name!=null)+msg+=" in "+name;+if(failOnError)+fail(msg);+else+System.out.println(msg);+}+protectedstaticvoidcopyFile(finalFilesrc,finalFiledst)throwsIOException{finalFileInputStreamfis=newFileInputStream(src);
@@ -186,7 +199,7 @@ public void setUp() throws Exception {super.setUp();configure();finalStringname=getClass().getName()+"."+getName();-recursiveDelete(trashParent,true,name);+recursiveDelete(trashParent,true,name,false);// Cleanup old failed stufftrash=newFile(trashParent,"trash"+System.currentTimeMillis()+"."+(testcount++));trash_git=newFile(trash,".git");if(shutdownhook==null){
@@ -201,7 +214,7 @@ public void run() {System.gc();for(Runnabler:shutDownCleanups)r.run();-recursiveDelete(trashParent,false,null);+recursiveDelete(trashParent,false,null,false);}};Runtime.getRuntime().addShutdownHook(shutdownhook);
@@ -165,10 +165,14 @@ protected File writeTrashFile(final String name, final String data)protectedstaticvoidcheckFile(Filef,finalStringcheckData)throwsIOException{Readerr=newInputStreamReader(newFileInputStream(f),"ISO-8859-1");-char[]data=newchar[(int)f.length()];-if(f.length()!=r.read(data))-thrownewIOException("Internal error reading file data from "+f);-assertEquals(checkData,newString(data));+try{+char[]data=newchar[(int)f.length()];+if(f.length()!=r.read(data))+thrownewIOException("Internal error reading file data from "+f);+assertEquals(checkData,newString(data));+}finally{+r.close();+}}protectedRepositorydb;
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
These were abusing setup, resulting in lost resources. To
fix this we can abuse tearDown too.
Signed-off-by: Robin Rosenberg <redacted>
---
.../tst/org/spearce/jgit/lib/PackWriterTest.java | 3 +++
1 files changed, 3 insertions(+), 0 deletions(-)
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
Pass the test case name for easier tracking of the test case that
causes problems.
Signed-off-by: Robin Rosenberg <redacted>
---
.../org/spearce/jgit/lib/RepositoryTestCase.java | 36 ++++++++++++++++----
1 files changed, 29 insertions(+), 7 deletions(-)
@@ -96,21 +96,42 @@ protected void configure() {*@paramdir*/protectedstaticvoidrecursiveDelete(finalFiledir){+recursiveDelete(dir,false,null);+}++protectedstaticbooleanrecursiveDelete(finalFiledir,booleansilent,+finalStringname){+if(!dir.exists())+returnsilent;finalFile[]ls=dir.listFiles();if(ls!=null){for(intk=0;k<ls.length;k++){finalFilee=ls[k];if(e.isDirectory()){-recursiveDelete(e);+silent=recursiveDelete(e,silent,name);}else{-e.delete();+if(!e.delete()){+if(!silent){+Stringmsg="Warning: Failed to delete "+e;+if(name!=null)+msg+=" in "+name;+System.out.println(msg);+}+silent=true;+}}}}-dir.delete();-if(dir.exists()){-System.out.println("Warning: Failed to delete "+dir);+if(!dir.delete()){+if(!silent){+Stringmsg="Warning: Failed to delete "+dir;+if(name!=null)+msg+=" in "+name;+System.out.println(msg);+}+silent=true;}+returnsilent;}protectedstaticvoidcopyFile(finalFilesrc,finalFiledst)
From: Robin Rosenberg <hidden> Date: 2016-06-15 22:45:42
Automatically clean up any repositories created by the test cases.
Cleanup is attempted at the end of each test, but if that fails
Shutdown hooks attempt to clean up when the JVM exits. If memmory
mapping is enabled (disabled by default in unit tests), gc is
invoked to make it more likely that cleanup will occur successfully.
The drawback is that this is much slower, which is the reason we
disble memory mapping by default in unit tests.
Signed-off-by: Robin Rosenberg <redacted>
---
.../org/spearce/jgit/lib/RepositoryTestCase.java | 62 ++++++++++++++++----
1 files changed, 50 insertions(+), 12 deletions(-)
@@ -180,14 +185,23 @@ public void setUp() throws Exception {recursiveDelete(trashParent,true,name);trash=newFile(trashParent,"trash"+System.currentTimeMillis()+"."+(testcount++));trash_git=newFile(trash,".git");--Runtime.getRuntime().addShutdownHook(newThread(){-@Override-publicvoidrun(){-recursiveDelete(trashParent,false,name);-}-});-+if(shutdownhook==null){+shutdownhook=newThread(){+@Override+publicvoidrun(){+// 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(Runnabler:shutDownCleanups)+r.run();+recursiveDelete(trashParent,false,null);+}+};+Runtime.getRuntime().addShutdownHook(shutdownhook);+}db=newRepository(trash_git);db.create();
@@ -213,6 +227,22 @@ copyFile(JGitTestUtil.getTestResourceFile(packs[k] + ".idx"), new File(packDir,protectedvoidtearDown()throwsException{db.close();+for(Repositoryr: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();++finalStringname=getClass().getName()+"."+getName();+recursiveDelete(trash,false,name);+for(Repositoryr:repositoriesToClose)+recursiveDelete(r.getWorkDir(),false,name);++repositoriesToClose.clear();+super.tearDown();}
From: Johannes Schindelin <hidden> Date: 2016-06-15 22:45:43
Hi,
On Sat, 29 Nov 2008, Robin Rosenberg wrote:
[Repository refactoring] Would be cool, but having that diff engine is
more important to me.
Stay tuned. I have something that outputs something resembling a diff
now. Of course, the output is not correct yet, due to bugs I introduced
cunnily when trying to fix another bug.
I'll keep you posted,
Dscho
From: Shawn O. Pearce <hidden> Date: 2016-06-15 22:45:43
Robin Rosenberg [off-list ref] wrote:
A completele reworked set of patches, including fixing a couple
more forgot-to-close bugs and Shawns suggestion that we disable
memory mapping in junit tests by default.
...
Robin Rosenberg (8):
Drop unneeded code in unit tests
Cleanup malformed test cases
Turn off memory mapping in JGit unit tests by default
Add a counter to make sure the test repo name is unique
Make the cleanup less verbose when it fails to delete temporary
stuff.
Cleanup after each test.
Close files opened by unit testing framework
Hard failure on unit test cleanups if they fail.
.../tst/org/spearce/jgit/lib/PackWriterTest.java | 3 +
.../org/spearce/jgit/lib/RepositoryTestCase.java | 152 +++++++++++++++++---
.../tst/org/spearce/jgit/lib/T0007_Index.java | 10 +-
3 files changed, 139 insertions(+), 26 deletions(-)
Merged. The series looked really good to me. Second time is the
charm, eh? :-)
--
Shawn.