/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.framework.core; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeHighlighting.Pass; import com.intellij.codeInsight.completion.CodeCompletionHandlerBase; import com.intellij.codeInsight.completion.CompletionType; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.ImplicitUsageProvider; import com.intellij.codeInsight.daemon.ProblemHighlightFilter; import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.quickFix.LightQuickFixTestCase; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.IntentionManager; import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler; import com.intellij.codeInspection.InspectionProfileEntry; import com.intellij.codeInspection.InspectionToolProvider; import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.ModifiableModel; import com.intellij.codeInspection.ex.InspectionProfileImpl; import com.intellij.codeInspection.ex.InspectionTool; import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; import com.intellij.ide.startup.StartupManagerEx; import com.intellij.ide.startup.impl.StartupManagerImpl; import com.intellij.lang.ExternalAnnotatorsFilter; import com.intellij.lang.LanguageAnnotators; import com.intellij.lang.StdLanguages; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ex.PathManagerEx; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.InspectionProfileManager; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.search.IndexPatternBuilder; import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry; import com.intellij.psi.xml.XmlFileNSInfoProvider; import com.intellij.testFramework.FileTreeAccessFilter; import com.intellij.testFramework.LightPlatformTestCase; import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; import com.intellij.util.IncorrectOperationException; import com.intellij.xml.XmlSchemaProvider; import gnu.trove.THashMap; import gnu.trove.TIntArrayList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; public abstract class DaemonAnalyzerTestCase extends CodeInsightTestCase { private final Map<String, LocalInspectionTool> myAvailableTools = new THashMap<>(); private final Map<String, LocalInspectionToolWrapper> myAvailableLocalTools = new THashMap<>(); private boolean toInitializeDaemon; private final FileTreeAccessFilter myFileTreeAccessFilter = new FileTreeAccessFilter(); @Override protected void beforeMethod() throws Exception { super.beforeMethod(); //((VirtualFilePointerManagerImpl) VirtualFilePointerManager.getInstance()).cleanupForNextTest(); final LocalInspectionTool[] tools = configureLocalInspectionTools(); for (LocalInspectionTool tool : tools) { enableInspectionTool(tool); } final InspectionProfileImpl profile = new InspectionProfileImpl(LightPlatformTestCase.PROFILE) { @Override @NotNull public ModifiableModel getModifiableModel() { mySource = this; return this; } @Override @NotNull public InspectionProfileEntry[] getInspectionTools(PsiElement element) { final Collection<LocalInspectionToolWrapper> tools = myAvailableLocalTools.values(); return tools.toArray(new LocalInspectionToolWrapper[tools.size()]); } @Override public boolean isToolEnabled(@Nullable HighlightDisplayKey key, PsiElement element) { return key != null && myAvailableTools.containsKey(key.toString()); } @NotNull @Override public HighlightDisplayLevel getErrorLevel(@NotNull HighlightDisplayKey key, PsiElement element) { final LocalInspectionTool localInspectionTool = myAvailableTools.get(key.toString()); return localInspectionTool != null ? localInspectionTool.getDefaultLevel() : HighlightDisplayLevel.WARNING; } @Override public InspectionTool getInspectionTool(@NotNull String shortName, @NotNull PsiElement element) { return myAvailableLocalTools.get(shortName); } }; final InspectionProfileManager inspectionProfileManager = InspectionProfileManager.getInstance(); inspectionProfileManager.addProfile(profile); inspectionProfileManager.setRootProfile(LightPlatformTestCase.PROFILE); Disposer.register(getProject(), new Disposable() { @Override public void dispose() { inspectionProfileManager.deleteProfile(LightPlatformTestCase.PROFILE); } }); InspectionProjectProfileManager.getInstance(getProject()).updateProfile(profile); InspectionProjectProfileManager.getInstance(getProject()).setProjectProfile(profile.getName()); DaemonCodeAnalyzerImpl daemonCodeAnalyzer = (DaemonCodeAnalyzerImpl) DaemonCodeAnalyzer.getInstance(getProject()); toInitializeDaemon = !isRunning(daemonCodeAnalyzer); daemonCodeAnalyzer.prepareForTest(); final StartupManagerImpl startupManager = (StartupManagerImpl) StartupManagerEx.getInstanceEx(getProject()); startupManager.runStartupActivities(); startupManager.startCacheUpdate(); startupManager.runPostStartupActivities(); DaemonCodeAnalyzerSettings.getInstance().setImportHintEnabled(false); if (isPerformanceTest()) { IntentionManager.getInstance().getAvailableIntentionActions(); // hack to avoid slowdowns in PyExtensionFactory PathManagerEx.getTestDataPath(); // to cache stuff ReferenceProvidersRegistry.getInstance(); // preload tons of classes InjectedLanguageManager.getInstance(getProject()); // zillion of Dom Sem classes LanguageAnnotators.INSTANCE.allForLanguage(StdLanguages.JAVA); // pile of annotator classes loads LanguageAnnotators.INSTANCE.allForLanguage(StdLanguages.XML); ProblemHighlightFilter.EP_NAME.getExtensions(); Extensions.getExtensions(ImplicitUsageProvider.EP_NAME); Extensions.getExtensions(XmlSchemaProvider.EP_NAME); Extensions.getExtensions(XmlFileNSInfoProvider.EP_NAME); Extensions.getExtensions(ExternalAnnotatorsFilter.EXTENSION_POINT_NAME); Extensions.getExtensions(IndexPatternBuilder.EP_NAME); } } private boolean isRunning(DaemonCodeAnalyzerImpl daemonCodeAnalyzer) { try { Method method = daemonCodeAnalyzer.getClass().getDeclaredMethod("isRunning"); method.setAccessible(true); return (Boolean)method.invoke(daemonCodeAnalyzer); } catch (Exception e) { throw new RuntimeException(e); } } @Override protected void afterMethod() throws Exception { ((StartupManagerImpl) StartupManager.getInstance(getProject())).checkCleared(); if (toInitializeDaemon) { ((DaemonCodeAnalyzerImpl) DaemonCodeAnalyzer.getInstance(getProject())).cleanupAfterTest(false); } super.afterMethod(); // ((VirtualFilePointerManagerImpl) VirtualFilePointerManager.getInstance()).assertPointersDisposed(); } @Override protected boolean isRunInWriteAction() { return false; } protected void enableInspectionTool(@NotNull LocalInspectionTool tool) { final String shortName = tool.getShortName(); final HighlightDisplayKey key = HighlightDisplayKey.find(shortName); if (key == null) { HighlightDisplayKey.register(shortName, tool.getDisplayName(), tool.getID()); } myAvailableTools.put(shortName, tool); myAvailableLocalTools.put(shortName, new LocalInspectionToolWrapper(tool)); } protected void enableInspectionToolsFromProvider(@NotNull InspectionToolProvider toolProvider) { try { for (Class c : toolProvider.getInspectionClasses()) { enableInspectionTool((LocalInspectionTool) c.newInstance()); } } catch (Exception e) { throw new RuntimeException(e); } } protected void disableInspectionTool(String shortName) { myAvailableTools.remove(shortName); myAvailableLocalTools.remove(shortName); } protected LocalInspectionTool[] configureLocalInspectionTools() { return LocalInspectionTool.EMPTY_ARRAY; } protected LocalInspectionTool[] createLocalInspectionTools(@NotNull final InspectionToolProvider... provider) { final ArrayList<LocalInspectionTool> result = new ArrayList<>(); for (InspectionToolProvider toolProvider : provider) { for (Class aClass : toolProvider.getInspectionClasses()) { try { final Object tool = aClass.newInstance(); assertTrue(tool instanceof LocalInspectionTool); result.add((LocalInspectionTool) tool); } catch (Exception e) { LOG.error(e); } } } return result.toArray(new LocalInspectionTool[result.size()]); } // protected Collection<HighlightInfo> doDoTest(boolean checkWarnings, boolean checkInfos) { // return doDoTest(checkWarnings, checkInfos, false); // } // // protected Collection<HighlightInfo> doDoTest(boolean checkWarnings, boolean checkInfos, boolean checkWeakWarnings) { // return checkHighlighting(new ExpectedHighlightingData(getCurrentEditor().getDocument(), checkWarnings, checkWeakWarnings, checkInfos, getCurrentFile())); // } // // protected Collection<HighlightInfo> checkHighlighting(final ExpectedHighlightingData data) { // PsiDocumentManager.getInstance(myProject).commitAllDocuments(); // // //to load text // ApplicationManager.getApplication().runWriteAction(new Runnable() { // public void run() { // TreeUtil.clearCaches((TreeElement) getCurrentFile().getNode()); // } // }); // // // //to initialize caches // if (!DumbService.isDumb(getProject())) { // myPsiManager.getCacheManager().getFilesWithWord("XXX", UsageSearchContext.IN_COMMENTS, GlobalSearchScope.allScope(myProject), true); // } // // Collection<HighlightInfo> infos = doHighlighting(); // String text = getCurrentEditor().getDocument().getText(); // data.checkLineMarkers(DaemonCodeAnalyzerImpl.getLineMarkers(getDocument(getCurrentFile()), getProject()), text); // data.checkResult(infos, text); // return infos; // } public void allowTreeAccessForFile(final VirtualFile file) { myFileTreeAccessFilter.allowTreeAccessForFile(file); } @NotNull protected Collection<HighlightInfo> highlightErrors() { return filter(doHighlighting(), HighlightSeverity.ERROR); } @NotNull protected List<HighlightInfo> doHighlighting() { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); TIntArrayList toIgnore = new TIntArrayList(); if (!doTestLineMarkers()) { toIgnore.add(Pass.UPDATE_OVERRIDEN_MARKERS); toIgnore.add(Pass.VISIBLE_LINE_MARKERS); toIgnore.add(Pass.LINE_MARKERS); } if (!doExternalValidation()) { toIgnore.add(Pass.EXTERNAL_TOOLS); } if (forceExternalValidation()) { toIgnore.add(Pass.LINE_MARKERS); toIgnore.add(Pass.LOCAL_INSPECTIONS); toIgnore.add(Pass.POPUP_HINTS); toIgnore.add(Pass.POST_UPDATE_ALL); toIgnore.add(Pass.UPDATE_ALL); toIgnore.add(Pass.UPDATE_OVERRIDEN_MARKERS); toIgnore.add(Pass.VISIBLE_LINE_MARKERS); } boolean canChange = canChangeDocumentDuringHighlighting(); List<HighlightInfo> infos = CodeInsightTestFixtureImpl.instantiateAndRun(getCurrentFile(), getCurrentEditor(), toIgnore.toNativeArray(), canChange); if (!canChange) { Document document = getDocument(getCurrentFile()); ((DaemonCodeAnalyzerImpl) DaemonCodeAnalyzer.getInstance(getProject())).getFileStatusMap().assertAllDirtyScopesAreNull(document); } return infos; } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface CanChangeDocumentDuringHighlighting { } private boolean canChangeDocumentDuringHighlighting() { return annotatedWith(CanChangeDocumentDuringHighlighting.class); } @NotNull public static List<HighlightInfo> filter(@NotNull final List<HighlightInfo> infos, HighlightSeverity minSeverity) { ArrayList<HighlightInfo> result = new ArrayList<>(); for (final HighlightInfo info : infos) { if (info.getSeverity().compareTo(minSeverity) >= 0) result.add(info); } return result; } protected boolean doTestLineMarkers() { return false; } protected boolean doExternalValidation() { return true; } protected boolean forceExternalValidation() { return false; } protected static void findAndInvokeIntentionAction(@NotNull final Collection<HighlightInfo> infos, String intentionActionName, @NotNull final Editor editor, @NotNull final PsiFile file) throws IncorrectOperationException { IntentionAction intentionAction = findIntentionAction(infos, intentionActionName, editor, file); assertNotNull(intentionActionName, intentionAction); assertTrue(ShowIntentionActionsHandler.chooseActionAndInvoke(file, editor, intentionAction, intentionActionName)); } protected static void cannotFindAndInvokeIntentionAction(@NotNull final Collection<HighlightInfo> infos, String intentionActionName, @NotNull final Editor editor, @NotNull final PsiFile file) throws IncorrectOperationException { IntentionAction intentionAction = findIntentionAction(infos, intentionActionName, editor, file); assertNull(intentionActionName, intentionAction); } protected static IntentionAction findIntentionAction(@NotNull final Collection<HighlightInfo> infos, final String intentionActionName, @NotNull final Editor editor, @NotNull final PsiFile file) { List<IntentionAction> actions = LightQuickFixTestCase.getAvailableActions(editor, file); IntentionAction intentionAction = LightQuickFixTestCase.findActionWithText(actions, intentionActionName); if (intentionAction == null) { final List<IntentionAction> availableActions = new ArrayList<>(); for (HighlightInfo info : infos) { if (info.quickFixActionRanges != null) { for (Pair<HighlightInfo.IntentionActionDescriptor, TextRange> pair : info.quickFixActionRanges) { IntentionAction action = pair.first.getAction(); if (action.isAvailable(file.getProject(), editor, file)) availableActions.add(action); } } } intentionAction = LightQuickFixTestCase.findActionWithText( availableActions, intentionActionName ); } return intentionAction; } // public void checkHighlighting(Editor editor, boolean checkWarnings, boolean checkInfos) { // setActiveEditor(editor); // doDoTest(checkWarnings, checkInfos); // } protected void completeBasic() { new CodeCompletionHandlerBase(CompletionType.BASIC).invokeCompletion(myProject, getCurrentEditor(), 1, false, false); } }