Flaky Listener Leak detected on interface DocumentListener

Answered

Hi there

I have a test that relies on `HeavyPlatformTestCase`, however the test sometimes fails for reasons that escape me at this time. I suppose there's a timing issue around line status listener removal possibly due to slower hardware, higher contention, and that when it happens it's 99% in a CI job.

the stack trace shows a leak about a line status track listener however the test don't use any `LineStatusTracker` explicitly.

java.lang.AssertionError: Listeners leaked for interface com.intellij.openapi.editor.event.DocumentListener:
[com.intellij.openapi.vcs.impl.LineStatusTrackerManager$MyDocumentListener@26503ffb]
at org.junit.Assert.fail(Assert.java:88)
at com.intellij.testFramework.EditorListenerTracker.checkListenersLeak(EditorListenerTracker.java:60)
at com.intellij.testFramework.HeavyPlatformTestCase.lambda$tearDown$14(HeavyPlatformTestCase.java:511)
at com.intellij.testFramework.RunAll.run(RunAll.java:58)
at com.intellij.testFramework.RunAll.run(RunAll.java:50)
at com.intellij.testFramework.RunAll.runAll(RunAll.java:24)
at com.intellij.testFramework.HeavyPlatformTestCase.tearDown(HeavyPlatformTestCase.java:470)
at com.datadog.intellij.dependency.gradle.GradleBasedHeavyTestCase.lambda$tearDown$6(GradleBasedHeavyTestCase.java:82)
at com.intellij.testFramework.RunAll.run(RunAll.java:58)
at com.intellij.testFramework.RunAll.run(RunAll.java:50)
at com.datadog.intellij.dependency.gradle.GradleBasedHeavyTestCase.tearDown(GradleBasedHeavyTestCase.java:83)
at com.intellij.testFramework.HeavyPlatformTestCase.lambda$runBareImpl$23(HeavyPlatformTestCase.java:627)
at com.intellij.testFramework.EdtTestUtil.lambda$runInEdtAndWait$1(EdtTestUtil.java:40)

Here's the code that is used to setup and tear down the test :

abstract class GradleBasedHeavyTestCase extends HeavyPlatformTestCase {
protected IdeaProjectTestFixture myTestFixture;
private VirtualFile myProjectRoot;
private Path testTempDir;
private VirtualFile wrapperPropertiesFile;
private VirtualFile buildFile;

@Override
protected void setUp() throws Exception {
super.setUp();
myTestFixture = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(getName(), false).getFixture();
myTestFixture.setUp();
myProject = myTestFixture.getProject();

testTempDir = Files.createTempDirectory("testDir-" + getName());
var projectDir = Files.createDirectories(testTempDir.resolve("project")).toFile();
myProjectRoot = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(projectDir);
}

@Override
protected void tearDown() {
new RunAll(
() -> buildFile = null,
() -> wrapperPropertiesFile = null,
() -> myProject = null,
() -> myTestFixture.tearDown(),
() -> PathKt.delete(testTempDir, true),
super::tearDown // <====== assertions from here
).run();
}

I have looked at `com.intellij.openapi.vcs.impl.LineStatusTrackerManager#startListenForEditors` where this listener seems to be registered, and it seems to be invoked on the `ApplicationImpl` thread pool, which seems triggered along the way when the test case is run with `com.intellij.testFramework.HeavyPlatformTestCase#runBareRunnable` then `runInEdtAndWait`.

I'm quite confused about what I should do to avoid this error. Any help or suggestion would be welcome.

The plugin targets IJ SDK 2021.1.

0
3 comments

For reference the leak detecting code, works by first getting a list of all the listeners already in place (probably set by the test infrastructure) before the test method run, and after the test has been run it lists the listeners residual listeners minus the one identified before.

Debugging confirmed this listener is setup very early, before the actual test is being run, it is setup in a different thread (the Application pool from IJ itself).

I believe this could in certain situation make the registration of this specific listener happens after the leak detector’s which is then remaining once the tests finishes, and thus causing the exception.

0

For reference the issue was i my test setup, in the above code there is this line 

myProject = myTestFixture.getProject();

which replaces the project reference created by HeavyPlatformTestCase, this project has to be cleaned up in the HeavyPlatformTestCase::tearDown method.

The solution was to be restore this unused reference before test teardown.

 

0

Please sign in to leave a comment.