Files
openide/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/HighlightingMarkupGraveTest.java
Alexandr Trushev 291a54cc98 IJPL-158885 Migrate highlighting grave to Necromancer API
GitOrigin-RevId: ced652528104a7f7d8a0fd8a354b8f2eb712433e
2024-07-23 19:03:05 +00:00

183 lines
8.2 KiB
Java

// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl;
import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.application.impl.LaterInvocator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.editor.impl.DocumentMarkupModel;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.MarkupModel;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.testFramework.CoroutineKt;
import com.intellij.testFramework.LeakHunter;
import com.intellij.util.TestTimeOut;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ref.GCWatcher;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.intellij.codeInsight.daemon.impl.HelperKt.openTextEditorForDaemonTest;
import static com.intellij.openapi.fileEditor.impl.EditorHistoryManagerTestKt.overrideFileEditorManagerImplementation;
public class HighlightingMarkupGraveTest extends DaemonAnalyzerTestCase {
@Override
protected boolean doTestLineMarkers() {
return true;
}
public static class MyStoppableAnnotator extends DaemonAnnotatorsRespondToChangesTest.MyRecordingAnnotator {
private static final String SWEARING = "No swearing";
private final AtomicBoolean allowToRun = new AtomicBoolean(true);
@Override
public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) {
while (!allowToRun.get()) {
Thread.yield();
}
if (element instanceof PsiComment && element.getText().equals("//XXX")) {
holder.newAnnotation(HighlightSeverity.ERROR, SWEARING).range(element).create();
iDidIt();
}
}
}
@Override
protected void setUpProject() throws Exception {
overrideFileEditorManagerImplementation(FileEditorManagerImpl.class, getTestRootDisposable());
super.setUpProject();
}
public void testSymbolSeverityHighlightersAreAppliedOnFileReload() {
HighlightingNecromancer.runInEnabled(() -> {
MyStoppableAnnotator annotator = new MyStoppableAnnotator();
DaemonAnnotatorsRespondToChangesTest.useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyStoppableAnnotator[]{annotator}, () -> {
@Language("JAVA")
String text = """
class ClassName {
//XXX
int fieldName;
void methodName(ClassName paramName) {}
}""";
configureByText(JavaFileType.INSTANCE, text);
assertEquals(MyStoppableAnnotator.SWEARING, assertOneElement(highlightErrors()).getDescription());
assertEquals("//XXX", assertOneElement(highlightErrors()).highlighter.getTextRange().substring(getFile().getText()));
VirtualFile virtualFile = getFile().getVirtualFile();
closeEditorAndEnsureTheDocumentMarkupIsGced(virtualFile);
annotator.allowToRun.set(false);
// reload file editor, and check the stored highlighters are reloaded back and applied, before the highlighting (MyStoppableAnnotator in particular) is run
try {
TextEditor textEditor = openTextEditorForDaemonTest(myProject, virtualFile);
assertNotNull(textEditor);
Document document = textEditor.getEditor().getDocument();
MarkupModel markupModel = DocumentMarkupModel.forDocument(document, getProject(), true);
CoroutineKt.executeSomeCoroutineTasksAndDispatchAllInvocationEvents(myProject);
List<String> symbolHighlighters =
Arrays.stream(markupModel.getAllHighlighters())
.filter(h -> h.getTextAttributesKey() != null)
.filter(h -> h.getLayer() == HighlighterLayer.ADDITIONAL_SYNTAX)
.filter(h -> HighlightingNecromancer.isZombieMarkup(h))
.sorted(RangeMarker.BY_START_OFFSET)
.map(h -> h.getTextRange().substring(document.getText()))
.toList();
assertNotEmpty(symbolHighlighters);
assertEquals("[ClassName, fieldName, methodName, ClassName, paramName]", symbolHighlighters.toString());
}
finally {
annotator.allowToRun.set(true);
}
});
});
}
private void closeEditorAndEnsureTheDocumentMarkupIsGced(@NotNull VirtualFile virtualFile) {
// close editor, and clear all references retaining this document
// (to make sure the DocumentMarkupModel is really recreated and populated with stored highlighters, not preserved since the previous highlighting run)
FileDocumentManager.getInstance().saveAllDocuments();
FileEditorManager.getInstance(myProject).closeFile(virtualFile);
HighlightingNecromancer.clearSpawnedZombies(getProject());
myFile = null;
myEditor = null;
TestTimeOut t = TestTimeOut.setTimeout(100 * 2, TimeUnit.MILLISECONDS);
while (!t.isTimedOut()) {
CoroutineKt.executeSomeCoroutineTasksAndDispatchAllInvocationEvents(myProject);
LaterInvocator.purgeExpiredItems();
LaterInvocator.dispatchPendingFlushes();
((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).getUpdateProgress().clear();
}
try {
GCWatcher.tracking(FileDocumentManager.getInstance().getDocument(virtualFile)).ensureCollected();
}
catch (IllegalStateException e) {
LeakHunter.checkLeak(LeakHunter.allRoots(), DocumentImpl.class, doc -> FileDocumentManager.getInstance().getFile(doc) == virtualFile);
throw e;
}
}
public void testStoredHighlightersAreAppliedImmediatelyOnFileReload() {
HighlightingNecromancer.runInEnabled(() -> {
MyStoppableAnnotator annotator = new MyStoppableAnnotator();
DaemonAnnotatorsRespondToChangesTest.useAnnotatorsIn(JavaFileType.INSTANCE.getLanguage(), new MyStoppableAnnotator[]{annotator}, () -> {
@Language("JAVA")
String text = """
class X {
//XXX
}""";
configureByText(JavaFileType.INSTANCE, text);
assertEquals(MyStoppableAnnotator.SWEARING, assertOneElement(highlightErrors()).getDescription());
assertEquals("//XXX", assertOneElement(highlightErrors()).highlighter.getTextRange().substring(getFile().getText()));
VirtualFile virtualFile = getFile().getVirtualFile();
closeEditorAndEnsureTheDocumentMarkupIsGced(virtualFile);
annotator.allowToRun.set(false);
// reload file editor, and check the stored highlighters are reloaded back and applied, before the highlighting (MyStoppableAnnotator in particular) is run
try {
TextEditor textEditor = openTextEditorForDaemonTest(myProject, virtualFile);
assertNotNull(textEditor);
Document document = textEditor.getEditor().getDocument();
MarkupModel markupModel = DocumentMarkupModel.forDocument(document, getProject(), true);
CoroutineKt.executeSomeCoroutineTasksAndDispatchAllInvocationEvents(myProject);
RangeHighlighter
errorHighlighter = ContainerUtil.find(markupModel.getAllHighlighters(), h -> CodeInsightColors.ERRORS_ATTRIBUTES.equals(h.getTextAttributesKey()));
assertNotNull(errorHighlighter);
assertEquals("//XXX", errorHighlighter.getTextRange().substring(document.getText()));
assertTrue(HighlightingNecromancer.isZombieMarkup(errorHighlighter));
}
finally {
annotator.allowToRun.set(true);
}
});
});
}
}