From aab47de9b768694c39744c6d6abc1033c2cd6c02 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Wed, 11 Dec 2019 00:17:33 +0300 Subject: [PATCH] fix occasional test fail when the future is completed successfully but the status is not set to EXECUTED yet, check for test annotators actually annotate something GitOrigin-RevId: 93f8dd0391ae62e02102eecfe12bc940816fa3dd --- .../advHighlighting/InjectedAnnotator.xml | 2 +- .../impl/DaemonRespondToChangesTest.java | 65 ++++++++++++++----- .../LightAnnotatorHighlightingTest.java | 64 ++++++++---------- .../daemon/impl/DaemonCodeAnalyzerImpl.java | 5 +- .../intellij/concurrency/JobLauncherImpl.java | 21 +++--- 5 files changed, 88 insertions(+), 69 deletions(-) diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/InjectedAnnotator.xml b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/InjectedAnnotator.xml index 2608a489bcec..d7df64fa6900 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/InjectedAnnotator.xml +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/InjectedAnnotator.xml @@ -1,5 +1,5 @@ aaa="dddd"> -   +   \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java index c754e56a9dd5..4fcf6add8716 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/DaemonRespondToChangesTest.java @@ -2134,8 +2134,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { } public void testAddRemoveHighlighterRaceInIncorrectAnnotatorsWhichUseFileRecursiveVisit() { - Annotator annotator = new MyIncorrectlyRecursiveAnnotator(); - useAnnotatorsIn(new Annotator[]{annotator}, () -> { + useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new MyRecordingAnnotator[]{new MyIncorrectlyRecursiveAnnotator()}, () -> { @Language("JAVA") String text1 = "class X {\n" + " int foo(Object param) {\n" + @@ -2158,30 +2157,35 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { }); } - public static void useAnnotatorsIn(@NotNull Annotator[] annotators, @NotNull Runnable runnable) { - com.intellij.lang.Language java = StdFileTypes.JAVA.getLanguage(); + public static void useAnnotatorsIn(@NotNull com.intellij.lang.Language language, + @NotNull MyRecordingAnnotator[] annotators, + @NotNull Runnable runnable) { + MyRecordingAnnotator.clearAll(); for (Annotator annotator : annotators) { - LanguageAnnotators.INSTANCE.addExplicitExtension(java, annotator); + LanguageAnnotators.INSTANCE.addExplicitExtension(language, annotator); } try { - List list = LanguageAnnotators.INSTANCE.allForLanguage(java); + List list = LanguageAnnotators.INSTANCE.allForLanguage(language); assertTrue(list.toString(), list.containsAll(Arrays.asList(annotators))); runnable.run(); + for (MyRecordingAnnotator annotator : annotators) { + assertTrue(annotator +" must have done something but didn't", annotator.didIDoIt()); + } } finally { for (int i = annotators.length - 1; i >= 0; i--) { Annotator annotator = annotators[i]; - LanguageAnnotators.INSTANCE.removeExplicitExtension(java, annotator); + LanguageAnnotators.INSTANCE.removeExplicitExtension(language, annotator); } } - List list = LanguageAnnotators.INSTANCE.allForLanguage(java); + List list = LanguageAnnotators.INSTANCE.allForLanguage(language); for (Annotator annotator : annotators) { assertFalse(list.toString(), list.contains(annotator)); } } - public static class MyIncorrectlyRecursiveAnnotator implements Annotator { + public static class MyIncorrectlyRecursiveAnnotator extends MyRecordingAnnotator { Random random = new Random(); @Override public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder holder) { @@ -2192,6 +2196,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { if (Objects.equals(keyword.getText(), "this")) { holder.createAnnotation(HighlightSeverity.WARNING, keyword.getTextRange(), "XXX"); TimeoutUtil.sleep(random.nextInt(100)); + iDidIt(); } } }); @@ -2383,7 +2388,23 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { } private static final AtomicInteger toSleepMs = new AtomicInteger(0); - public static class MySleepyAnnotator implements Annotator { + public abstract static class MyRecordingAnnotator implements Annotator { + protected static final Set> done = ContainerUtil.newConcurrentSet(); + protected void iDidIt() { + done.add(getClass()); + } + boolean didIDoIt() { + return done.contains(getClass()); + } + static void clearAll() { + done.clear(); + } + } + public static class MySleepyAnnotator extends MyRecordingAnnotator { + public MySleepyAnnotator() { + iDidIt(); // is not supposed to ever do anything + } + @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (element instanceof PsiClass) { // must be after MyFastAnnotator annotated the comment @@ -2394,33 +2415,39 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { } } } - public static class MyFastAnnotator implements Annotator { + public static class MyFastAnnotator extends MyRecordingAnnotator { private static final String SWEARING = "No swearing"; @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (element instanceof PsiComment && element.getText().equals("//XXX")) { holder.createErrorAnnotation(element.getTextRange(), SWEARING); + iDidIt(); } } } - public static class MyInfoAnnotator implements Annotator { + public static class MyInfoAnnotator extends MyRecordingAnnotator { @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (element instanceof PsiComment && ((PsiComment)element).getTokenType().equals(JavaTokenType.C_STYLE_COMMENT)) { holder.createInfoAnnotation(element.getTextRange(), "comment"); + iDidIt(); } } } public void testAddAnnotationToHolderEntailsCreatingCorrespondingRangeHighlighterMoreOrLessImmediately() { if (!ensureEnoughParallelism()) return; - useAnnotatorsIn(new Annotator[]{new MyInfoAnnotator(), new MySleepyAnnotator(), new MyFastAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately); - useAnnotatorsIn(new Annotator[]{new MySleepyAnnotator(), new MyInfoAnnotator(), new MyFastAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately); - useAnnotatorsIn(new Annotator[]{new MySleepyAnnotator(), new MyFastAnnotator(), new MyInfoAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately); + useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new MyRecordingAnnotator[]{new MyInfoAnnotator(), new MySleepyAnnotator(), new MyFastAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately + ); + useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new MyRecordingAnnotator[]{new MySleepyAnnotator(), new MyInfoAnnotator(), new MyFastAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately + ); + useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new MyRecordingAnnotator[]{new MySleepyAnnotator(), new MyFastAnnotator(), new MyInfoAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately + ); // also check in the opposite order in case the order of annotators is important - useAnnotatorsIn(new Annotator[]{new MyFastAnnotator(), new MyInfoAnnotator(), new MySleepyAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately); + useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new MyRecordingAnnotator[]{new MyFastAnnotator(), new MyInfoAnnotator(), new MySleepyAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately + ); } private void checkSwearingAnnotationIsVisibleImmediately() { @@ -2472,7 +2499,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { } } - public static class MyNewBuilderAnnotator implements Annotator { + public static class MyNewBuilderAnnotator extends MyRecordingAnnotator { @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (element instanceof PsiComment && element.getText().equals("//XXX")) { @@ -2483,6 +2510,7 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { while (toSleepMs.addAndGet(-100) > 0) { TimeoutUtil.sleep(100); } + iDidIt(); } } } @@ -2497,7 +2525,8 @@ public class DaemonRespondToChangesTest extends DaemonAnalyzerTestCase { public void testAddAnnotationViaBuilderEntailsCreatingCorrespondingRangeHighlighterImmediately() { if (!ensureEnoughParallelism()) return; - useAnnotatorsIn(new Annotator[]{new MyNewBuilderAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately); + useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new MyRecordingAnnotator[]{new MyNewBuilderAnnotator(), }, this::checkSwearingAnnotationIsVisibleImmediately + ); } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAnnotatorHighlightingTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAnnotatorHighlightingTest.java index 5e45900e0aa1..8ca9ca4b5dc0 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAnnotatorHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightAnnotatorHighlightingTest.java @@ -11,9 +11,10 @@ import com.intellij.codeInsight.daemon.impl.VisibleHighlightingPassFactory; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase; -import com.intellij.lang.Language; -import com.intellij.lang.LanguageAnnotators; -import com.intellij.lang.annotation.*; +import com.intellij.lang.annotation.Annotation; +import com.intellij.lang.annotation.AnnotationBuilder; +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.CodeInsightColors; import com.intellij.openapi.editor.ex.EditorEx; @@ -44,29 +45,13 @@ import java.util.function.Function; public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase { public void testInjectedAnnotator() { - Annotator annotator = new MyAnnotator(); - Language xml = StdFileTypes.XML.getLanguage(); - LanguageAnnotators.INSTANCE.addExplicitExtension(xml, annotator); - try { - List list = LanguageAnnotators.INSTANCE.allForLanguage(xml); - assertTrue(list.toString(), list.contains(annotator)); + DaemonRespondToChangesTest.useAnnotatorsIn(StdFileTypes.XML.getLanguage(), new DaemonRespondToChangesTest.MyRecordingAnnotator[]{new MyAnnotator()}, () -> { doTest(LightAdvHighlightingTest.BASE_PATH + "/" + getTestName(false) + ".xml",true,false); - } - finally { - LanguageAnnotators.INSTANCE.removeExplicitExtension(xml, annotator); - } - - List list = LanguageAnnotators.INSTANCE.allForLanguage(xml); - assertFalse(list.toString(), list.contains(annotator)); + }); } public void testAnnotatorWorksWithFileLevel() { - Annotator annotator = new MyTopFileAnnotator(); - Language java = StdFileTypes.JAVA.getLanguage(); - LanguageAnnotators.INSTANCE.addExplicitExtension(java, annotator); - try { - List list = LanguageAnnotators.INSTANCE.allForLanguage(java); - assertTrue(list.toString(), list.contains(annotator)); + DaemonRespondToChangesTest.useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new DaemonRespondToChangesTest.MyRecordingAnnotator[]{new MyTopFileAnnotator()}, () -> { configureByFile(LightAdvHighlightingTest.BASE_PATH + "/" + getTestName(false) + ".java"); ((EditorEx)getEditor()).getScrollPane().getViewport().setSize(new Dimension(1000,1000)); // whole file fit onscreen doHighlighting(); @@ -87,30 +72,28 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase assertEmpty(warnings); fileLevel = ((DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(getProject())).getFileLevelHighlights(getProject(), getFile()); assertEmpty(fileLevel); - } - finally { - LanguageAnnotators.INSTANCE.removeExplicitExtension(java, annotator); - } - - List list = LanguageAnnotators.INSTANCE.allForLanguage(java); - assertFalse(list.toString(), list.contains(annotator)); + }); } // must stay public for PicoContainer to work - public static class MyAnnotator implements Annotator { + public static final class MyAnnotator extends DaemonRespondToChangesTest.MyRecordingAnnotator { @Override public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder holder) { psiElement.accept(new XmlElementVisitor() { - @Override public void visitXmlTag(XmlTag tag) { + @Override + public void visitXmlTag(XmlTag tag) { XmlAttribute attribute = tag.getAttribute("aaa", ""); if (attribute != null) { holder.createWarningAnnotation(attribute, "MyAnnotator"); + iDidIt(); } } - @Override public void visitXmlToken(XmlToken token) { - if (token.getTokenType() == XmlTokenType.XML_ENTITY_REF_TOKEN) { + @Override + public void visitXmlToken(XmlToken token) { + if (token.getTokenType() == XmlTokenType.XML_CHAR_ENTITY_REF) { holder.createWarningAnnotation(token, "ENTITY"); + iDidIt(); } } }); @@ -118,18 +101,20 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase } // must stay public for PicoContainer to work - public static class MyTopFileAnnotator implements Annotator { + public static class MyTopFileAnnotator extends DaemonRespondToChangesTest.MyRecordingAnnotator { @Override public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder holder) { if (psiElement instanceof PsiFile && !psiElement.getText().contains("xxx")) { Annotation annotation = holder.createWarningAnnotation(psiElement, "top level"); annotation.setFileLevelAnnotation(true); + iDidIt(); } } } public void testAnnotatorMustNotSpecifyCrazyRangeForCreatedAnnotation() { - DaemonRespondToChangesTest.useAnnotatorsIn(new Annotator[]{new MyCrazyAnnotator()}, () -> runMyAnnotators()); + DaemonRespondToChangesTest.useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new DaemonRespondToChangesTest.MyRecordingAnnotator[]{new MyCrazyAnnotator()}, () -> runMyAnnotators() + ); } private void runMyAnnotators() { @org.intellij.lang.annotations.Language("JAVA") @@ -147,11 +132,12 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase .runPasses(getFile(), getEditor().getDocument(), Collections.singletonList(textEditor), ArrayUtilRt.EMPTY_INT_ARRAY, false, null); } - public static class MyCrazyAnnotator implements Annotator { + public static class MyCrazyAnnotator extends DaemonRespondToChangesTest.MyRecordingAnnotator { @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (element instanceof PsiComment && element.getText().equals("//XXX")) { try { + iDidIt(); holder.newAnnotation(HighlightSeverity.ERROR, "xxx") .range(new TextRange(0,1)) .create(); @@ -224,7 +210,7 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase assertSame(newBuilder, builder); builder.registerFix().create(); } - public static class MyStupidRepetitiveAnnotator implements Annotator { + public static class MyStupidRepetitiveAnnotator extends DaemonRespondToChangesTest.MyRecordingAnnotator { @Override public void annotate(@NotNull PsiElement element, @NotNull AnnotationHolder holder) { if (element instanceof PsiComment && element.getText().equals("//XXX")) { @@ -258,11 +244,13 @@ public class LightAnnotatorHighlightingTest extends LightDaemonAnalyzerTestCase HighlightDisplayKey myDeadCodeKey = HighlightDisplayKey.findOrRegister(UnusedDeclarationInspectionBase.SHORT_NAME, UnusedDeclarationInspectionBase.DISPLAY_NAME, UnusedDeclarationInspectionBase.SHORT_NAME); checkThrowsWhenCalledTwiceOnFixBuilder(holder, fixBuilder -> fixBuilder.key(myDeadCodeKey)); checkThrowsWhenCalledTwiceOnFixBuilder(holder, fixBuilder -> fixBuilder.range(new TextRange(0,0))); + iDidIt(); } } } public void testAnnotationBuilderMethodsAllowedToBeCalledOnlyOnce() { - DaemonRespondToChangesTest.useAnnotatorsIn(new Annotator[]{new MyStupidRepetitiveAnnotator()}, () -> runMyAnnotators()); + DaemonRespondToChangesTest.useAnnotatorsIn(StdFileTypes.JAVA.getLanguage(), new DaemonRespondToChangesTest.MyRecordingAnnotator[]{new MyStupidRepetitiveAnnotator()}, () -> runMyAnnotators() + ); } } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java index 4d4a5abbee46..273eeb311551 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java @@ -323,7 +323,8 @@ public final class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implement } myUpdateRunnableFuture.cancel(false); - waitForTermination(); // previous passes can be canceled but still in flight. wait for them to avoid interference + // previous passes can be canceled but still in flight. wait for them to avoid interference + myPassExecutorService.cancelAll(false); fileStatusMap.allowDirt(canChangeDocument); final DaemonProgressIndicator progress = createUpdateProgress(map.keySet()); myPassExecutorService.submitPasses(map, progress); @@ -350,7 +351,7 @@ public final class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzerEx implement HighlightingSessionImpl session = (HighlightingSessionImpl)HighlightingSessionImpl.getOrCreateHighlightingSession(file, progress, null); if (!waitInOtherThread(60000, canChangeDocument)) { - throw new TimeoutException("Unable to complete in 60s"); + throw new TimeoutException("Unable to complete in 60s. Thread dump:\n"+ThreadDumper.dumpThreadsToString()); } session.waitForHighlightInfosApplied(); UIUtil.dispatchAllInvocationEvents(); diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java index 8b16d43e9765..9ee1923aa010 100644 --- a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java +++ b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java @@ -3,7 +3,6 @@ package com.intellij.concurrency; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationUtil; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; @@ -26,7 +25,6 @@ import java.util.concurrent.atomic.AtomicBoolean; * @author cdr */ public class JobLauncherImpl extends JobLauncher { - private static final Logger LOG = Logger.getInstance(JobLauncherImpl.class); static final int CORES_FORK_THRESHOLD = 1; @Override @@ -215,23 +213,26 @@ public class JobLauncherImpl extends JobLauncher { // waits for the job to finish execution (when called on a canceled job in the middle of the execution, wait for finish) @Override - public void waitForCompletion(int millis) throws InterruptedException, ExecutionException, TimeoutException { + public void waitForCompletion(int millis) throws InterruptedException, TimeoutException { + long timeout = System.currentTimeMillis() + millis; while (!isDone()) { + long toWait = timeout - System.currentTimeMillis(); + if (toWait < 0) { + throw new TimeoutException(); + } try { - myForkJoinTask.get(millis, TimeUnit.MILLISECONDS); - break; + myForkJoinTask.get(toWait, TimeUnit.MILLISECONDS); } catch (CancellationException e) { // was canceled in the middle of execution - // can't do anything but wait. help other tasks in the meantime - if (!isDone()) { - ForkJoinPool.commonPool().awaitQuiescence(millis, TimeUnit.MILLISECONDS); - if (!isDone()) throw new TimeoutException(); - } } catch (ExecutionException e) { ExceptionUtil.rethrow(e.getCause()); } + // can't do anything but wait. help other tasks in the meantime + if (!isDone()) { + ForkJoinPool.commonPool().awaitQuiescence(toWait, TimeUnit.MILLISECONDS); + } } } }