Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Wolf2016-03-02 22:04:00 +0000
committerThomas Wolf2016-03-13 17:12:47 +0000
commite47bced605873c5e82705f9a8184f540d1dbb586 (patch)
treec9959fa5936b5c54ca91abba02f0da90f570386b /org.eclipse.egit.ui.test
parent3f18b3527b1628a021f72939c249304a4628b4ac (diff)
downloadegit-e47bced605873c5e82705f9a8184f540d1dbb586.tar.gz
egit-e47bced605873c5e82705f9a8184f540d1dbb586.tar.xz
egit-e47bced605873c5e82705f9a8184f540d1dbb586.zip
RepositoryCache: do not prematurely remove submodules
Basically we have no way of knowing when we no longer reference a repository. In our code alone, there may be RepositoryMappings and RepositoryNodes directly referencing a Repository, but some views may also have direct references to Repository instances. We therefore cannot explicitly remove repositories from the RepositoryCache, since we have no efficient and 100% reliable way to determine whether a Repository is still in use somewhere. Therefore the RepositoryCache relies on weak reference semantics. Before https://git.eclipse.org/r/#/c/62066/ , this whole mechanism was broken anyway because the IndexDiffCache had no way to remove an IndexDiffCacheEntry instance, and those instances had hard references to the Repository. So once there was an IndexDiffCacheEntry for a repository, that Repository instance would be kept forever. https://git.eclipse.org/r/#/c/62066/ itself was a wrong approach because it neglected that some repositories might never be "configured" repositories visible in the Repositories view. Such repositories would be removed from the RepositoryCache while still in use. Submodules and nested repositories are affected by this, but so can top-level repositories. The approach taken in this change here fixes this. First, we go back to relying solely on the weak reference semantics of the RepositoryCache. Note that doing so does not give any guarantee about when exactly a no longer used and only weakly reachable Repository instance will actually be removed from the cache. Then we at least make sure that we don't keep any hard references around. That's more difficult than it may seem: * Replaced all hard references to Repository in IndexDiffCacheEntry. We now only use the repository's git directory, and use that to get the repository from the RepositoryCache, if it still is there. * The oldIndex DirCache in an IndexDiffCacheEntry also had a hard reference to the Repository. Use a DirCache.read() variant that doesn't set that link -- it's used only for writing a DirCache, which we don't do. Note that this is a bit fragile as it relies on an implementation detail of JGit, but for now I see no other way. * Even worse, some Eclipse internals do keep around hard references to some "last selection"s. Those may contain no longer used RepositoryNodes from the repository view, which still reference the Repository instance through a hard reference. We have no real way to reliably ensure that these Eclipse internals forget those nodes. Therefore we have to ensure in RemoveCommand that we actually do null out these Repository references once we're sure we have removed the node from our view. (The two places I found where Eclipse holds on to such defunct nodes are WorkbenchSourceProvider.lastShowInSelection, set when the "Shown In..." context menu was last filled, and the CommonViewer, which also remembers the last selection.) * Our own RepositorySourceProvider had a private field referencing the last selected Repository. The RepositorySourceProvider is a singleton that is instantiated very early and then kept around forever. That was resolved by using a weak reference for the repository. * The EclipseContext also managed to (indirectly) hold on to a hard reference to a Repository instance through the context variable we provided. That was solved by not passing the Repository directly as the context variable defined by RepositorySourceProvider but again only the git directory. * RebaseInteractivePlan has a static global cache of plans and each plan had a hard reference to a repository. A plan is computed when the view is opened, even if never executed. That accumulated hard references to repositories. Solved by using a weak reference. * The Eclipse resource tree's DataTreeLookup has a static cache of instances that are re-used but not cleared after use. Those may keep references to our RepositoryMapping session properties for some time after the IResource to with the property was attached has gone. The test explicitly accounts for this. In the full test run in maven, more problems showed up in a heap dump taken just before we test for no cached repositories in GitRepositoriesViewRepoDeletionTest: numerous FileRepository instances from earlier tests were still referenced. * The EclipseContext retains some handler activations of ActionHamdlers of anonymous Action subclasses of SpellcheckableMessageArea, which reference the area through this$0, which itself keeps a reference to the CommitDialog through this$0, which means we keep the CommitMessageComponent, which has a hard reference to the Repository. Solved by using static subclasses that reference only the SourceViewer. * The Eclipse NavigationHistory keeps around references to some CommitEditorInputs, which also have a hard reference to a repository. * The synchronize view references a repository through its GitSynchronizeData. Resolved in test by keeping the synchronize view closed. * The FileRepository from testCompareWithPreviousWithMerge was still referenced from the job from CompareWithPreviousActionHandler even though no such job was running anymore. Referenced in the ProgressMonitorFocusJobDialog, which was still kept around through its fontChangeListener (an inner non-static class in the ultimate ancestor class Window), which apparently somehow was still registered.. Unclear why or what happened there. Not resolved. * Same thing with testRevertFailure; referenced from RevertCommitOperation from the job in RevertHandler from ProgressMonitorFocusJobDialog. Unresolved. * Anonymous actions in SwitchToMenu still reference a long gone repository from test method selectionWithProj1. Unclear why and unresolved. * Some repositories from earlier tests were still referenced through long defunct RepositoryNode instances. Unresolved. * RepositoryPropertySourceProvider has a hard reference to its lastObject, and the RepositoryPropertySource has hard references to the configs, which may have hard references to the Repository. Resolved in test by closing the property sheet; unresolved in general. Because we can't explicitly remove items from the RepositoryCache, we also cannot explicitly remove IndexDiffCache entries. The only thing we can do is to ensure we remove IndexDiffCacheEntries when we detect that a repository in the cache no longer exists (has been garbage collected, or its git directory no longer exists.) Additionally, the resource change listener of an IndexDiffCacheEntry unregisters itself when it finds its repository has gone. I cannot really claim that this still fixes bug 483664 because there is absolutely no way to ensure that repositories vanish from the RepositoryCache in a timely manner. But it's a best-effort attempt to at least try, and at the same time not to evict repositories from the cache prematurely. The test explicitly invokes System.gc() in an attempt to make the JVM actually reclaim weakly reachable objects. This is not guaranteed, but appears to work in practice: the test thus only shows that the obvious places where we kept hard references are indeed resolved, and the repository does indeed vanish eventually. But see the "unresolved" items above: there's no guarantee that some view or action handler or Eclipse internal class doesn't somehow still manages to keep a hard reference and thus prevent reclamation. Finally, testing for an empty RepositoryCache must ensure that the RepositoryChangeScanner does not interfere; otherwise that may temporarily hold hard references to repositories. Solved using a scheduling rule. Change-Id: I3f437caccd58d6c9fb4187f66d9f53e7834a5224 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.egit.ui.test')
-rw-r--r--org.eclipse.egit.ui.test/META-INF/MANIFEST.MF7
-rw-r--r--org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/LocalRepositoryTestCase.java45
-rw-r--r--org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/gitflow/AbstractGitflowHandlerTest.java6
-rw-r--r--org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java46
-rw-r--r--org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/view/repositories/GitRepositoriesViewRepoDeletionTest.java143
5 files changed, 232 insertions, 15 deletions
diff --git a/org.eclipse.egit.ui.test/META-INF/MANIFEST.MF b/org.eclipse.egit.ui.test/META-INF/MANIFEST.MF
index c2eed536e3..88c0eac8ad 100644
--- a/org.eclipse.egit.ui.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.egit.ui.test/META-INF/MANIFEST.MF
@@ -48,9 +48,10 @@ Import-Package: org.eclipse.egit.core.test;version="[4.3.0,4.4.0)",
org.eclipse.swtbot.swt.finder.utils,
org.eclipse.swtbot.swt.finder.waits,
org.eclipse.swtbot.swt.finder.widgets,
- org.junit;version="[4.3.1,5.0.0)",
- org.junit.runner;version="[4.3.1,5.0.0)",
- org.junit.runners;version="[4.3.1,5.0.0)",
+ org.junit;version="[4.7.0,5.0.0)",
+ org.junit.rules;version="[4.7.0,5.0.0)",
+ org.junit.runner;version="[4.7.0,5.0.0)",
+ org.junit.runners;version="[4.7.0,5.0.0)",
org.mockito;version="[1.8.0,1.9.0)",
org.mockito.stubbing;version="[1.8.0,1.9.0)",
org.osgi.framework;version="[1.4.0,2.0.0)"
diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/LocalRepositoryTestCase.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/LocalRepositoryTestCase.java
index 22c2784ef4..ad75191040 100644
--- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/LocalRepositoryTestCase.java
+++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/common/LocalRepositoryTestCase.java
@@ -36,6 +36,7 @@ import org.eclipse.egit.core.GitCorePreferences;
import org.eclipse.egit.core.GitProvider;
import org.eclipse.egit.core.JobFamilies;
import org.eclipse.egit.core.RepositoryCache;
+import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffCache;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.core.op.AddToIndexOperation;
@@ -47,7 +48,12 @@ import org.eclipse.egit.core.project.GitProjectData;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.test.TestUtils;
import org.eclipse.egit.ui.UIPreferences;
+import org.eclipse.egit.ui.internal.dialogs.CompareTreeView;
import org.eclipse.egit.ui.internal.push.PushOperationUI;
+import org.eclipse.egit.ui.internal.rebase.RebaseInteractiveView;
+import org.eclipse.egit.ui.internal.reflog.ReflogView;
+import org.eclipse.egit.ui.internal.repository.RepositoriesView;
+import org.eclipse.egit.ui.internal.staging.StagingView;
import org.eclipse.egit.ui.test.ContextMenuHelper;
import org.eclipse.egit.ui.test.Eclipse;
import org.eclipse.egit.ui.test.TestUtil;
@@ -69,10 +75,14 @@ import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.eclipse.team.core.RepositoryProvider;
+import org.eclipse.team.ui.history.IHistoryView;
+import org.eclipse.team.ui.synchronize.ISynchronizeView;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.rules.TestName;
/**
* Base class for testing with local (file-system based) repositories
@@ -140,20 +150,39 @@ public abstract class LocalRepositoryTestCase extends EGitTestCase {
protected static final String FILE2 = "test2.txt";
-
-
protected final static TestUtils testUtils = new TestUtils();
+ private static final String[] VIEWS_TO_CLOSE = { //
+ RebaseInteractiveView.VIEW_ID, //
+ ISynchronizeView.VIEW_ID, //
+ IHistoryView.VIEW_ID, //
+ CompareTreeView.ID, //
+ ReflogView.VIEW_ID, //
+ StagingView.VIEW_ID, //
+ RepositoriesView.VIEW_ID, //
+ "org.eclipse.search.ui.views.SearchView", //
+ "org.eclipse.ui.views.PropertySheet"
+ };
+
+ @Rule
+ public TestName testName = new TestName();
+
public File getTestDirectory() {
return testDirectory;
}
+ protected static void closeGitViews() {
+ for (String viewId : VIEWS_TO_CLOSE) {
+ TestUtil.hideView(viewId);
+ }
+ }
+
@Before
public void initNewTestDirectory() throws Exception {
testMethodNumber++;
// create standalone temporary directory
testDirectory = testUtils.createTempDir("LocalRepositoriesTests"
- + testMethodNumber);
+ + testMethodNumber + '_' + testName.getMethodName());
if (testDirectory.exists())
FileUtils.delete(testDirectory, FileUtils.RECURSIVE
| FileUtils.RETRY);
@@ -174,6 +203,7 @@ public abstract class LocalRepositoryTestCase extends EGitTestCase {
TestUtil.processUIEvents();
// close all editors/dialogs
new Eclipse().reset();
+ closeGitViews();
TestUtil.processUIEvents();
// cleanup
for (IProject project : ResourcesPlugin.getWorkspace().getRoot()
@@ -201,6 +231,7 @@ public abstract class LocalRepositoryTestCase extends EGitTestCase {
.getPreferenceStore()
.setValue(UIPreferences.SHOW_DETACHED_HEAD_WARNING,
false);
+ closeGitViews();
}
@AfterClass
@@ -208,11 +239,17 @@ public abstract class LocalRepositoryTestCase extends EGitTestCase {
testUtils.deleteTempDirs();
}
- protected static void shutDownRepositories() {
+ protected static void shutDownRepositories() throws Exception {
RepositoryCache cache = Activator.getDefault().getRepositoryCache();
for(Repository repository:cache.getAllRepositories())
repository.close();
cache.clear();
+ IEclipsePreferences prefs = Activator.getDefault().getRepositoryUtil()
+ .getPreferences();
+ synchronized (prefs) {
+ prefs.put(RepositoryUtil.PREFS_DIRECTORIES, "");
+ prefs.flush();
+ }
}
protected static void deleteAllProjects() throws Exception {
diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/gitflow/AbstractGitflowHandlerTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/gitflow/AbstractGitflowHandlerTest.java
index 03b5ee58e2..e048977827 100644
--- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/gitflow/AbstractGitflowHandlerTest.java
+++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/gitflow/AbstractGitflowHandlerTest.java
@@ -37,6 +37,7 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
+import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
@@ -59,6 +60,11 @@ public abstract class AbstractGitflowHandlerTest extends LocalRepositoryTestCase
resetPreferences();
}
+ @After
+ public void teardown() {
+ repository = null;
+ }
+
private void resetPreferences() {
IPreferenceStore prefStore = Activator.getDefault()
.getPreferenceStore();
diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java
index 320344a6f9..7b77a7fa40 100644
--- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java
+++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java
@@ -50,6 +50,7 @@ import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IViewPart;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -86,6 +87,8 @@ public class SubmoduleFolderTest extends LocalRepositoryTestCase {
parentRepositoryGitDir = createProjectAndCommitToRepository();
childRepositoryGitDir = createProjectAndCommitToRepository(CHILDREPO,
CHILDPROJECT);
+ Activator.getDefault().getRepositoryUtil()
+ .addConfiguredRepository(parentRepositoryGitDir);
parentRepository = lookupRepository(parentRepositoryGitDir);
childRepository = lookupRepository(childRepositoryGitDir);
parentProject = ResourcesPlugin.getWorkspace().getRoot()
@@ -137,6 +140,21 @@ public class SubmoduleFolderTest extends LocalRepositoryTestCase {
assertNotNull(subRepository);
}
+ @After
+ public void removeConfiguredRepositories() {
+ if (parentRepositoryGitDir != null) {
+ Activator.getDefault().getRepositoryUtil()
+ .removeDir(parentRepositoryGitDir);
+ }
+ if (childRepositoryGitDir != null) {
+ Activator.getDefault().getRepositoryUtil()
+ .removeDir(childRepositoryGitDir);
+ }
+ childRepository = null;
+ parentRepository = null;
+ subRepository = null;
+ }
+
@Test
public void testChildProjectMapsToSubRepo() {
RepositoryMapping mapping = RepositoryMapping.getMapping(childProject);
@@ -185,6 +203,7 @@ public class SubmoduleFolderTest extends LocalRepositoryTestCase {
TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE);
IndexDiffCacheEntry cache = Activator.getDefault().getIndexDiffCache()
.getIndexDiffCacheEntry(subRepository);
+ TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE);
IResourceState state = ResourceStateFactory.getInstance()
.get(cache.getIndexDiff(), file);
assertTrue("File should be staged", state.isStaged());
@@ -261,6 +280,24 @@ public class SubmoduleFolderTest extends LocalRepositoryTestCase {
node.getText().contains("[child"));
}
+ /**
+ * Tests that unrelated changes to the configured repositories do not
+ * prematurely remove submodules from the cache.
+ */
+ @Test
+ public void testRepoRemoval() {
+ Activator.getDefault().getRepositoryUtil()
+ .addConfiguredRepository(childRepositoryGitDir);
+ assertTrue("Should still have the subrepo in the cache",
+ containsRepo(Activator.getDefault().getRepositoryCache()
+ .getAllRepositories(), subRepository));
+ assertTrue("Should have changed the preference", Activator.getDefault()
+ .getRepositoryUtil().removeDir(childRepositoryGitDir));
+ assertTrue("Should still have the subrepo in the cache",
+ containsRepo(Activator.getDefault().getRepositoryCache()
+ .getAllRepositories(), subRepository));
+ }
+
@SuppressWarnings("restriction")
@Test
public void testHistoryFromProjectExplorerIsFromSubRepository()
@@ -283,6 +320,15 @@ public class SubmoduleFolderTest extends LocalRepositoryTestCase {
2);
}
+ private boolean containsRepo(Repository[] repositories, Repository needle) {
+ for (Repository repo : repositories) {
+ if (needle.equals(repo)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void assertRowCountInHistory(String msg, int expected)
throws Exception {
SWTBotView historyBot = TestUtil.showHistoryView();
diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/view/repositories/GitRepositoriesViewRepoDeletionTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/view/repositories/GitRepositoriesViewRepoDeletionTest.java
index 730b0739dc..0399bed316 100644
--- a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/view/repositories/GitRepositoriesViewRepoDeletionTest.java
+++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/view/repositories/GitRepositoriesViewRepoDeletionTest.java
@@ -17,11 +17,22 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.util.Arrays;
import java.util.List;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffCache;
import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.JobFamilies;
+import org.eclipse.egit.ui.internal.RepositoryCacheRule;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.test.ContextMenuHelper;
import org.eclipse.egit.ui.test.TestUtil;
@@ -32,6 +43,7 @@ import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotCheckBox;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
+import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -149,21 +161,109 @@ public class GitRepositoriesViewRepoDeletionTest extends
TestUtil.joinJobs(JobFamilies.REPOSITORY_DELETE);
refreshAndWait();
assertEmpty();
+
assertTrue(repositoryFile.exists());
assertTrue(
new File(repositoryFile.getParentFile(), PROJ1).isDirectory());
+ waitForDecorations();
+ closeGitViews();
+ TestUtil.waitForJobs(500, 5000);
+ // Session properties are stored in the Eclipse resource tree as part of
+ // the resource info. org.eclipse.core.internal.dtree.DataTreeLookup has
+ // a static LRU cache of lookup instances to avoid excessive strain on
+ // the garbage collector due to constantly allocating and then
+ // forgetting instances. These lookup objects may contain things
+ // recently queried or modified in the resource tree, such as session
+ // properties. As a result, the session properties of a deleted resource
+ // remain around a little longer than expected: to be precise, exactly
+ // 100 more queries on the Eclipse resource tree until the entry
+ // containing the session property is recycled. We use session
+ // properties to store the RepositoryMappings, which reference the
+ // repository.
+ //
+ // Make sure we clear that cache:
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ IProject project = root.getProject(PROJ1);
+ for (int i = 0; i < 101; i++) {
+ // Number of iterations at least DataTreeLookup.POOL_SIZE!
+ // Use up one DataTreeLookup instance:
+ project.create(null);
+ if (i == 0) {
+ // Furthermore, the WorkbenchSourceProvider has still a
+ // reference to the last selection, which is our now long
+ // removed repository node! Arguably that's a strange thing, but
+ // strictly speaking, since there is no guarantee _when_ a
+ // weakly referenced object is removed, not even making
+ // WorkbenchSourceProvider.lastShowInSelection a WeakReference
+ // might help. Therefore, let's make sure that the last "show
+ // in" selection is no longer the RepositoryNode, which also
+ // still has a reference to the repository. That last "show in"
+ // selection is set when the "Shown in..." context menu is
+ // filled, which happens when the project explorer's context
+ // menu is activated. So we have to open that menu at least once
+ // with a different selection.
+ SWTBotTree explorerTree = TestUtil.getExplorerTree();
+ SWTBotTreeItem projectNode = TestUtil.navigateTo(explorerTree,
+ PROJ1);
+ projectNode.select();
+ ContextMenuHelper.isContextMenuItemEnabled(explorerTree, "New");
+ }
+ project.delete(true, true, null);
+ }
+ TestUtil.waitForJobs(500, 5000);
+ // And we may have the RepositoryChangeScanner running: use a job
+ // with a scheduling rule that ensures we have exclusive access.
+ final String[] results = { null, null };
+ Job verifier = new Job(testName.getMethodName()) {
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ // Wait for things to definitely quieten down. Note that
+ // waitForJobs only waits for running and waiting jobs, there
+ // may still be scheduled jobs that might wake up and run after
+ // that. TestUtil.joinJobs does really join, which also waits
+ // for scheduled jobs.
+ try {
+ TestUtil.joinJobs(
+ org.eclipse.egit.core.JobFamilies.INDEX_DIFF_CACHE_UPDATE);
+ // Is this job doing something when the view is hidden?
+ TestUtil.joinJobs(JobFamilies.REPO_VIEW_REFRESH);
+ waitForDecorations();
+ } catch (InterruptedException e) {
+ results[0] = "Interrupted";
+ Thread.currentThread().interrupt();
+ return Status.CANCEL_STATUS;
+ }
+ // Finally... Java does not give any guarantees about when
+ // exactly an only weakly reachable object is finalized and
+ // garbage collected.
+ waitForFinalization(5000);
+ // Experience shows that an explicit garbage collection run very
+ // often does reclaim only weakly reachable objects and set the
+ // weak references' referents to null, but not even that can be
+ // guaranteed! Whether or not it does may also depend on the
+ // configuration of the JVM (such as through command-line
+ // arguments).
+ Repository[] repositories = org.eclipse.egit.core.Activator
+ .getDefault().getRepositoryCache().getAllRepositories();
+ results[0] = Arrays.asList(repositories).toString();
+ IndexDiffCache indexDiffCache = org.eclipse.egit.core.Activator
+ .getDefault().getIndexDiffCache();
+ results[1] = indexDiffCache.currentCacheEntries().toString();
+ return Status.OK_STATUS;
+ }
+
+ };
+ verifier.setRule(new RepositoryCacheRule());
+ verifier.setSystem(true);
+ verifier.schedule();
+ verifier.join();
List<String> configuredRepos = org.eclipse.egit.core.Activator
.getDefault().getRepositoryUtil().getConfiguredRepositories();
assertTrue("Expected no configured repositories",
configuredRepos.isEmpty());
- Repository[] repositories = org.eclipse.egit.core.Activator.getDefault()
- .getRepositoryCache().getAllRepositories();
- assertEquals("Expected no cached repositories", 0, repositories.length);
- // A pity we can't mock the cache.
- IndexDiffCache indexDiffCache = org.eclipse.egit.core.Activator
- .getDefault().getIndexDiffCache();
- assertTrue("Expected no IndexDiffCache entries",
- indexDiffCache.currentCacheEntries().isEmpty());
+ assertEquals("Expected no cached repositories", "[]", results[0]);
+ assertEquals("Expected no IndexDiffCache entries", "[]", results[1]);
}
@Test
@@ -217,4 +317,31 @@ public class GitRepositoriesViewRepoDeletionTest extends
assertFalse(subRepo.getWorkTree().exists());
}
+ @SuppressWarnings("restriction")
+ private void waitForDecorations() throws InterruptedException {
+ TestUtil.joinJobs(
+ org.eclipse.ui.internal.decorators.DecoratorManager.FAMILY_DECORATE);
+ }
+
+ /**
+ * Best-effort attempt to get finalization to occur.
+ *
+ * @param maxMillis
+ * maximum amount of time in milliseconds to try getting the
+ * garbage collector to finalize objects
+ */
+ private void waitForFinalization(int maxMillis) {
+ long stop = System.currentTimeMillis() + maxMillis;
+ MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
+ do {
+ System.gc();
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ } while (System.currentTimeMillis() < stop
+ && memoryBean.getObjectPendingFinalizationCount() > 0);
+ }
}

Back to the top