Enforce virtual file pointers leak tracking in fixtures;

moved tracking code away from VFPMI to VirtualFilePointerTracker because it's test-only
warn about not paired storePointers()/assertPointersAreDisposed() (memleaks otherwise from myStoredPointers)
(disable pointer leak tracking in FixtureRule.kt for now because only @develar knows what's going on there)
This commit is contained in:
Alexey Kudravtsev
2017-09-15 14:20:10 +03:00
parent e849391981
commit ced6ea877f
7 changed files with 140 additions and 43 deletions
@@ -19,8 +19,6 @@ import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.StdModuleTypes;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiClass;
import com.intellij.psi.impl.JavaPsiFacadeEx;
@@ -39,16 +37,12 @@ public abstract class IdeaTestCase extends PlatformTestCase {
super.setUp();
LanguageLevelProjectExtension.getInstance(getProject()).setLanguageLevel(LanguageLevel.JDK_1_6);
myJavaFacade = JavaPsiFacadeEx.getInstanceEx(myProject);
VirtualFilePointerManagerImpl filePointerManager = (VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance();
filePointerManager.storePointers();
}
@Override
protected void tearDown() throws Exception {
myJavaFacade = null;
super.tearDown();
VirtualFilePointerManagerImpl filePointerManager = (VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance();
filePointerManager.assertPointersAreDisposed();
}
public final JavaPsiFacadeEx getJavaFacade() {
@@ -15,6 +15,7 @@
*/
package com.intellij.openapi.vfs.impl;
import com.intellij.concurrency.ConcurrentCollectionFactory;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -35,7 +36,6 @@ import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.SmartList;
import com.intellij.concurrency.ConcurrentCollectionFactory;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.URLUtil;
import com.intellij.util.messages.MessageBus;
@@ -312,33 +312,8 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp
}
}
private final Set<VirtualFilePointerImpl> myStoredPointers = ContainerUtil.newIdentityTroveSet();
@TestOnly
public synchronized void storePointers() {
myStoredPointers.clear();
addAllPointersTo(myStoredPointers);
}
@TestOnly
public synchronized void assertPointersAreDisposed() {
List<VirtualFilePointerImpl> pointers = new ArrayList<>();
addAllPointersTo(pointers);
try {
for (VirtualFilePointerImpl pointer : pointers) {
if (!myStoredPointers.contains(pointer)) {
pointer.throwDisposalError("Virtual pointer '" + pointer +
"' hasn't been disposed: "+pointer.getStackTrace());
}
}
}
finally {
myStoredPointers.clear();
}
}
@TestOnly
private void addAllPointersTo(@NotNull Collection<VirtualFilePointerImpl> pointers) {
synchronized void addAllPointersTo(@NotNull Collection<VirtualFilePointerImpl> pointers) {
List<FilePointerPartNode> out = new ArrayList<>();
for (FilePointerPartNode root : myPointers.values()) {
root.addPointersUnder(null, false, "", out);
@@ -0,0 +1,109 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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 com.intellij.openapi.vfs.impl;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Tracks leaks of file pointers from {@link VirtualFilePointerManagerImpl}
* Usage:
*
* <pre>{@code
* class MyTest {
* VirtualFilePointerTracker myTracker;
* void setUpOrSomewhereBeforeTestExecution() {
* myTracker = new VirtualFilePointerTracker(); // all virtual file pointers created by this moment are remembered
* }
* void tearDownOrSomewhereAfterTestExecuted() {
* myTracker.assertPointersAreDisposed(); // throws if there are virtual file pointers created after setup but never disposed
* }
* }
* }</pre>
*/
@TestOnly
public class VirtualFilePointerTracker {
private static final Set<VirtualFilePointerImpl> storedPointers = ContainerUtil.newIdentityTroveSet();
private static Throwable trace;
private static boolean isTracking; // true when storePointers() was called but before assertPointersDisposed(). false otherwise
public VirtualFilePointerTracker() {
storePointers();
}
private synchronized void storePointers() {
if (isTracking) {
throw new IllegalStateException("Previous test did not call assertPointersAreDisposed() - see 'Caused by:' for its stacktrace", trace);
}
trace = new Throwable();
storedPointers.clear();
addAllPointersTo(storedPointers);
//System.out.println("VFPT.storePointers(" + storedPointers + ")");
isTracking = true;
}
public synchronized void assertPointersAreDisposed() {
if (!isTracking) {
throw new IllegalStateException("Double call of assertPointersAreDisposed() - see 'Caused by:' for the previous call", trace);
}
List<VirtualFilePointerImpl> pointers = new ArrayList<>();
addAllPointersTo(pointers);
//System.out.println("VFPT.assertPointersAreDisposed(" +pointers+")");
for (int i = pointers.size() - 1; i >= 0; i--) {
VirtualFilePointerImpl pointer = pointers.get(i);
if (storedPointers.remove(pointer)) {
pointers.remove(i);
}
}
try {
Set<VirtualFilePointerImpl> leaked = ContainerUtil.newTroveSet(new TObjectHashingStrategy<VirtualFilePointerImpl>() {
@Override
public int computeHashCode(VirtualFilePointerImpl pointer) {
return FileUtil.PATH_HASHING_STRATEGY.computeHashCode(pointer.getUrl());
}
@Override
public boolean equals(VirtualFilePointerImpl o1, VirtualFilePointerImpl o2) {
return FileUtil.PATH_HASHING_STRATEGY.equals(o1.getUrl(), o2.getUrl());
}
}, pointers);
leaked.removeAll(storedPointers);
for (VirtualFilePointerImpl pointer : leaked) {
pointer.throwDisposalError("Virtual pointer '" + pointer +
"' hasn't been disposed: " + pointer.getStackTrace());
}
}
finally {
storedPointers.clear();
trace = new Throwable();
isTracking = false;
}
}
private static void addAllPointersTo(@NotNull Collection<VirtualFilePointerImpl> pointers) {
((VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance()).addAllPointersTo(pointers);
}
}
@@ -91,7 +91,8 @@ class ProjectRule(val projectDescriptor: LightProjectDescriptor = LightProjectDe
}
}
(VirtualFilePointerManager.getInstance() as VirtualFilePointerManagerImpl).storePointers()
// TODO uncomment and figure out where to put this statement
// (VirtualFilePointerManager.getInstance() as VirtualFilePointerManagerImpl).storePointers()
return project
}
@@ -100,7 +101,8 @@ class ProjectRule(val projectDescriptor: LightProjectDescriptor = LightProjectDe
sharedProject = null
sharedModule = null
(ProjectManager.getInstance() as ProjectManagerImpl).forceCloseProject(project, true)
(VirtualFilePointerManager.getInstance() as VirtualFilePointerManagerImpl).assertPointersAreDisposed()
// TODO uncomment and figure out where to put this statement
// (VirtualFilePointerManager.getInstance() as VirtualFilePointerManagerImpl).assertPointersAreDisposed()
}
}
@@ -71,10 +71,9 @@ import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.openapi.vfs.encoding.EncodingManagerImpl;
import com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl;
import com.intellij.openapi.vfs.impl.VirtualFilePointerTracker;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
@@ -139,6 +138,8 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
PlatformTestUtil.registerProjectCleanup(LightPlatformTestCase::closeAndDeleteProject);
}
private VirtualFilePointerTracker myVirtualFilePointerTracker;
/**
* @return Project to be used in tests for example for project components retrieval.
*/
@@ -251,9 +252,6 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
ourSourceRoot = sourceRoot;
}
});
// project creation may make a lot of pointers, do not regard them as leak
((VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance()).storePointers();
}
/**
@@ -282,8 +280,7 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
myThreadTracker = new ThreadTracker();
ModuleRootManager.getInstance(ourModule).orderEntries().getAllLibrariesAndSdkClassesRoots();
VirtualFilePointerManagerImpl filePointerManager = (VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance();
filePointerManager.storePointers();
myVirtualFilePointerTracker = new VirtualFilePointerTracker();
});
}
@@ -390,7 +387,7 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
super::tearDown,
() -> myThreadTracker.checkLeak(),
() -> InjectedLanguageManagerImpl.checkInjectorsAreDisposed(project),
() -> ((VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance()).assertPointersAreDisposed()
() -> myVirtualFilePointerTracker.assertPointersAreDisposed()
).run();
}
@@ -58,11 +58,14 @@ import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl;
import com.intellij.openapi.vfs.impl.VirtualFilePointerTracker;
import com.intellij.openapi.vfs.impl.jar.JarFileSystemImpl;
import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl;
import com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
@@ -125,6 +128,7 @@ public abstract class PlatformTestCase extends UsefulTestCase implements DataPro
private static boolean ourPlatformPrefixInitialized;
private static Set<VirtualFile> ourEternallyLivingFilesCache;
private Sdk[] myOldSdks;
private VirtualFilePointerTracker myVirtualFilePointerTracker;
/**
* If a temp directory is reused from some previous test run, there might be cached children in its VFS.
@@ -224,6 +228,7 @@ public abstract class PlatformTestCase extends UsefulTestCase implements DataPro
DocumentCommitThread.getInstance().clearQueue();
UIUtil.dispatchAllInvocationEvents();
myVirtualFilePointerTracker = new VirtualFilePointerTracker();
}
public final Project getProject() {
@@ -518,6 +523,7 @@ public abstract class PlatformTestCase extends UsefulTestCase implements DataPro
})
.append(LightPlatformTestCase::checkEditorsReleased)
.append(() -> UsefulTestCase.checkForJdkTableLeaks(myOldSdks))
.append(() -> myVirtualFilePointerTracker.assertPointersAreDisposed())
.append(() -> {
myProjectManager = null;
myProject = null;
@@ -83,18 +83,22 @@ import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.ExtensionsArea;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.fileEditor.impl.EditorHistoryManager;
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.readOnlyHandler.ReadonlyStatusHandlerImpl;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.impl.VirtualFilePointerTracker;
import com.intellij.psi.*;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.impl.PsiManagerEx;
@@ -162,6 +166,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig
private ChooseByNameBase myChooseByNamePopup;
private boolean myAllowDirt;
private boolean myCaresAboutInjection = true;
private VirtualFilePointerTracker myVirtualFilePointerTracker;
public CodeInsightTestFixtureImpl(@NotNull IdeaProjectTestFixture projectFixture, @NotNull TempDirTestFixture tempDirTestFixture) {
myProjectFixture = projectFixture;
@@ -1206,6 +1211,11 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig
ensureIndexesUpToDate(getProject());
((StartupManagerImpl)StartupManagerEx.getInstanceEx(getProject())).runPostStartupActivities();
});
for (Module module : ModuleManager.getInstance(getProject()).getModules()) {
ModuleRootManager.getInstance(module).orderEntries().getAllLibrariesAndSdkClassesRoots(); // instantiate all VFPs
}
myVirtualFilePointerTracker = new VirtualFilePointerTracker();
}
@Override
@@ -1233,12 +1243,16 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig
}
finally {
super.tearDown();
myVirtualFilePointerTracker.assertPointersAreDisposed();
}
}
private void closeOpenFiles() {
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
FileEditorManagerEx.getInstanceEx(getProject()).closeAllFiles();
for (VirtualFile file : EditorHistoryManager.getInstance(getProject()).getFiles()) {
EditorHistoryManager.getInstance(getProject()).removeFile(file);
}
}
@NotNull