ae436c2f45d22ecfa7da8f57ee93b16d7bd1fdfc
[idea/community.git] / python / testSrc / com / jetbrains / python / fixtures / PyTestCase.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.jetbrains.python.fixtures;
17
18 import com.google.common.base.Joiner;
19 import com.intellij.codeInsight.intention.IntentionAction;
20 import com.intellij.codeInspection.LocalQuickFix;
21 import com.intellij.codeInspection.ex.QuickFixWrapper;
22 import com.intellij.execution.actions.ConfigurationContext;
23 import com.intellij.execution.actions.ConfigurationFromContext;
24 import com.intellij.execution.actions.RunConfigurationProducer;
25 import com.intellij.execution.configurations.RunConfiguration;
26 import com.intellij.find.findUsages.CustomUsageSearcher;
27 import com.intellij.find.findUsages.FindUsagesOptions;
28 import com.intellij.ide.DataManager;
29 import com.intellij.openapi.actionSystem.DataContext;
30 import com.intellij.openapi.actionSystem.IdeActions;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.command.CommandProcessor;
33 import com.intellij.openapi.command.WriteCommandAction;
34 import com.intellij.openapi.editor.Editor;
35 import com.intellij.openapi.editor.ex.EditorEx;
36 import com.intellij.openapi.extensions.Extensions;
37 import com.intellij.openapi.module.Module;
38 import com.intellij.openapi.roots.impl.FilePropertyPusher;
39 import com.intellij.openapi.util.Ref;
40 import com.intellij.openapi.util.TextRange;
41 import com.intellij.openapi.vfs.LocalFileSystem;
42 import com.intellij.openapi.vfs.VirtualFile;
43 import com.intellij.platform.DirectoryProjectConfigurator;
44 import com.intellij.psi.*;
45 import com.intellij.psi.codeStyle.CodeStyleManager;
46 import com.intellij.psi.codeStyle.CodeStyleSettings;
47 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
48 import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
49 import com.intellij.psi.search.searches.ReferencesSearch;
50 import com.intellij.refactoring.RefactoringActionHandler;
51 import com.intellij.testFramework.LightProjectDescriptor;
52 import com.intellij.testFramework.TestDataPath;
53 import com.intellij.testFramework.UsefulTestCase;
54 import com.intellij.testFramework.fixtures.*;
55 import com.intellij.testFramework.fixtures.impl.LightTempDirTestFixtureImpl;
56 import com.intellij.usageView.UsageInfo;
57 import com.intellij.usages.Usage;
58 import com.intellij.usages.rules.PsiElementUsage;
59 import com.intellij.util.CommonProcessors.CollectProcessor;
60 import com.intellij.util.IncorrectOperationException;
61 import com.jetbrains.python.PythonHelpersLocator;
62 import com.jetbrains.python.PythonLanguage;
63 import com.jetbrains.python.PythonTestUtil;
64 import com.jetbrains.python.documentation.PyDocumentationSettings;
65 import com.jetbrains.python.documentation.docstrings.DocStringFormat;
66 import com.jetbrains.python.formatter.PyCodeStyleSettings;
67 import com.jetbrains.python.psi.LanguageLevel;
68 import com.jetbrains.python.psi.PyClass;
69 import com.jetbrains.python.psi.PyFile;
70 import com.jetbrains.python.psi.PyUtil;
71 import com.jetbrains.python.psi.impl.PyFileImpl;
72 import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
73 import org.jetbrains.annotations.NotNull;
74 import org.jetbrains.annotations.Nullable;
75 import org.junit.Assert;
76
77 import java.io.File;
78 import java.util.*;
79
80 /**
81  * @author yole
82  */
83 @TestDataPath("$CONTENT_ROOT/../testData/")
84 public abstract class PyTestCase extends UsefulTestCase {
85   public static final String PYTHON_2_MOCK_SDK = "2.7";
86   public static final String PYTHON_3_MOCK_SDK = "3.4";
87
88   private static final PyLightProjectDescriptor ourPyDescriptor = new PyLightProjectDescriptor(PYTHON_2_MOCK_SDK);
89   protected static final PyLightProjectDescriptor ourPy3Descriptor = new PyLightProjectDescriptor(PYTHON_3_MOCK_SDK);
90   private static final String PARSED_ERROR_MSG = "Operations should have been performed on stubs but caused file to be parsed";
91
92   protected CodeInsightTestFixture myFixture;
93
94   @Nullable
95   protected static VirtualFile getVirtualFileByName(String fileName) {
96     final VirtualFile path = LocalFileSystem.getInstance().findFileByPath(fileName.replace(File.separatorChar, '/'));
97     if (path != null) {
98       refreshRecursively(path);
99       return path;
100     }
101     return null;
102   }
103
104   /**
105    * Reformats currently configured file.
106    */
107   protected final void reformatFile() {
108     WriteCommandAction.runWriteCommandAction(null, new Runnable() {
109       @Override
110       public void run() {
111         doPerformFormatting();
112       }
113     });
114   }
115
116   private void doPerformFormatting() throws IncorrectOperationException {
117     final PsiFile file = myFixture.getFile();
118     final TextRange myTextRange = file.getTextRange();
119     CodeStyleManager.getInstance(myFixture.getProject()).reformatText(file, myTextRange.getStartOffset(), myTextRange.getEndOffset());
120   }
121
122   @Override
123   protected void setUp() throws Exception {
124     super.setUp();
125     IdeaTestFixtureFactory factory = IdeaTestFixtureFactory.getFixtureFactory();
126     TestFixtureBuilder<IdeaProjectTestFixture> fixtureBuilder = factory.createLightFixtureBuilder(getProjectDescriptor());
127     final IdeaProjectTestFixture fixture = fixtureBuilder.getFixture();
128     myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(fixture,
129                                                                                     createTempDirFixture());
130     myFixture.setUp();
131
132     myFixture.setTestDataPath(getTestDataPath());
133   }
134
135   /**
136    * @return fixture to be used as temporary dir.
137    */
138   @NotNull
139   protected TempDirTestFixture createTempDirFixture() {
140     return new LightTempDirTestFixtureImpl(true); // "tmp://" dir by default
141   }
142
143   protected String getTestDataPath() {
144     return PythonTestUtil.getTestDataPath();
145   }
146
147   @Override
148   protected void tearDown() throws Exception {
149     setLanguageLevel(null);
150     myFixture.tearDown();
151     myFixture = null;
152     final PythonLanguageLevelPusher levelPusher = Extensions.findExtension(FilePropertyPusher.EP_NAME, PythonLanguageLevelPusher.class);
153     levelPusher.flushLanguageLevelCache();
154     super.tearDown();
155     clearFields(this);
156   }
157
158   @Nullable
159   protected LightProjectDescriptor getProjectDescriptor() {
160     return ourPyDescriptor;
161   }
162
163   protected PsiReference findReferenceBySignature(final String signature) {
164     int pos = findPosBySignature(signature);
165     return findReferenceAt(pos);
166   }
167
168   protected PsiReference findReferenceAt(int pos) {
169     return myFixture.getFile().findReferenceAt(pos);
170   }
171
172   protected int findPosBySignature(String signature) {
173     return PsiDocumentManager.getInstance(myFixture.getProject()).getDocument(myFixture.getFile()).getText().indexOf(signature);
174   }
175
176   protected void setLanguageLevel(@Nullable LanguageLevel languageLevel) {
177     PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), languageLevel);
178   }
179
180   protected void runWithLanguageLevel(@NotNull LanguageLevel languageLevel, @NotNull Runnable action) {
181     setLanguageLevel(languageLevel);
182     try {
183       action.run();
184     }
185     finally {
186       setLanguageLevel(null);
187     }
188   }
189
190   protected void runWithDocStringFormat(@NotNull DocStringFormat format, @NotNull Runnable runnable) {
191     final PyDocumentationSettings settings = PyDocumentationSettings.getInstance(myFixture.getModule());
192     final DocStringFormat oldFormat = settings.getFormat();
193     settings.setFormat(format);
194     try {
195       runnable.run();
196     }
197     finally {
198       settings.setFormat(oldFormat);
199     }
200   }
201
202   /**
203    * Searches for quickfix itetion by its class
204    *
205    * @param clazz quick fix class
206    * @param <T>   quick fix class
207    * @return quick fix or null if nothing found
208    */
209   @Nullable
210   public <T extends LocalQuickFix> T findQuickFixByClassInIntentions(@NotNull final Class<T> clazz) {
211
212     for (final IntentionAction action : myFixture.getAvailableIntentions()) {
213       if ((action instanceof QuickFixWrapper)) {
214         final QuickFixWrapper quickFixWrapper = (QuickFixWrapper)action;
215         final LocalQuickFix fix = quickFixWrapper.getFix();
216         if (clazz.isInstance(fix)) {
217           @SuppressWarnings("unchecked")
218           final T result = (T)fix;
219           return result;
220         }
221       }
222     }
223     return null;
224   }
225
226
227   protected static void assertNotParsed(PyFile file) {
228     assertNull(PARSED_ERROR_MSG, ((PyFileImpl)file).getTreeElement());
229   }
230
231   /**
232    * @param name
233    * @return class by its name from file
234    */
235   @NotNull
236   protected PyClass getClassByName(@NotNull final String name) {
237     return myFixture.findElementByText("class " + name, PyClass.class);
238   }
239
240   /**
241    * @see #moveByText(com.intellij.testFramework.fixtures.CodeInsightTestFixture, String)
242    */
243   protected void moveByText(@NotNull final String testToFind) {
244     moveByText(myFixture, testToFind);
245   }
246
247   /**
248    * Finds some text and moves cursor to it (if found)
249    *
250    * @param fixture    test fixture
251    * @param testToFind text to find
252    * @throws AssertionError if element not found
253    */
254   public static void moveByText(@NotNull final CodeInsightTestFixture fixture, @NotNull final String testToFind) {
255     final PsiElement element = fixture.findElementByText(testToFind, PsiElement.class);
256     assert element != null : "No element found by text: " + testToFind;
257     fixture.getEditor().getCaretModel().moveToOffset(element.getTextOffset());
258   }
259
260   /**
261    * Finds all usages of element. Works much like method in {@link com.intellij.testFramework.fixtures.CodeInsightTestFixture#findUsages(com.intellij.psi.PsiElement)},
262    * but supports {@link com.intellij.find.findUsages.CustomUsageSearcher} and {@link com.intellij.psi.search.searches.ReferencesSearch} as well
263    *
264    * @param element what to find
265    * @return usages
266    */
267   @NotNull
268   protected Collection<PsiElement> findUsage(@NotNull final PsiElement element) {
269     final Collection<PsiElement> result = new ArrayList<PsiElement>();
270     final CollectProcessor<Usage> usageCollector = new CollectProcessor<Usage>();
271     for (final CustomUsageSearcher searcher : CustomUsageSearcher.EP_NAME.getExtensions()) {
272       searcher.processElementUsages(element, usageCollector, new FindUsagesOptions(myFixture.getProject()));
273     }
274     for (final Usage usage : usageCollector.getResults()) {
275       if (usage instanceof PsiElementUsage) {
276         result.add(((PsiElementUsage)usage).getElement());
277       }
278     }
279     for (final PsiReference reference : ReferencesSearch.search(element).findAll()) {
280       result.add(reference.getElement());
281     }
282
283     for (final UsageInfo info : myFixture.findUsages(element)) {
284       result.add(info.getElement());
285     }
286
287     return result;
288   }
289
290   /**
291    * Returns elements certain element allows to navigate to (emulates CTRL+Click, actually).
292    * You need to pass element as argument or
293    * make sure your fixture is configured for some element (see {@link com.intellij.testFramework.fixtures.CodeInsightTestFixture#getElementAtCaret()})
294    *
295    * @param element element to fetch navigate elements from (may be null: element under caret would be used in this case)
296    * @return elements to navigate to
297    */
298   @NotNull
299   protected Set<PsiElement> getElementsToNavigate(@Nullable final PsiElement element) {
300     final Set<PsiElement> result = new HashSet<PsiElement>();
301     final PsiElement elementToProcess = ((element != null) ? element : myFixture.getElementAtCaret());
302     for (final PsiReference reference : elementToProcess.getReferences()) {
303       final PsiElement directResolve = reference.resolve();
304       if (directResolve != null) {
305         result.add(directResolve);
306       }
307       if (reference instanceof PsiPolyVariantReference) {
308         for (final ResolveResult resolveResult : ((PsiPolyVariantReference)reference).multiResolve(true)) {
309           result.add(resolveResult.getElement());
310         }
311       }
312     }
313     return result;
314   }
315
316   /**
317    * Clears provided file
318    *
319    * @param file file to clear
320    */
321   protected void clearFile(@NotNull final PsiFile file) {
322     CommandProcessor.getInstance().executeCommand(myFixture.getProject(), new Runnable() {
323       @Override
324       public void run() {
325         ApplicationManager.getApplication().runWriteAction(new Runnable() {
326           @Override
327           public void run() {
328             for (final PsiElement element : file.getChildren()) {
329               element.delete();
330             }
331           }
332         });
333       }
334     }, null, null);
335   }
336
337   /**
338    * Runs refactoring using special handler
339    *
340    * @param handler handler to be used
341    */
342   protected void refactorUsingHandler(@NotNull final RefactoringActionHandler handler) {
343     final Editor editor = myFixture.getEditor();
344     assertInstanceOf(editor, EditorEx.class);
345     handler.invoke(myFixture.getProject(), editor, myFixture.getFile(), ((EditorEx)editor).getDataContext());
346   }
347
348   /**
349    * Configures project by some path. It is here to emulate {@link com.intellij.platform.PlatformProjectOpenProcessor}
350    *
351    * @param path         path to open
352    * @param configurator configurator to use
353    */
354   protected void configureProjectByProjectConfigurators(@NotNull final String path,
355                                                         @NotNull final DirectoryProjectConfigurator configurator) {
356     final VirtualFile newPath =
357       myFixture.copyDirectoryToProject(path, String.format("%s%s%s", "temp_for_project_conf", File.pathSeparator, path));
358     final Ref<Module> moduleRef = new Ref<Module>(myFixture.getModule());
359     configurator.configureProject(myFixture.getProject(), newPath, moduleRef);
360   }
361
362   public static String getHelpersPath() {
363     return new File(PythonHelpersLocator.getPythonCommunityPath(), "helpers").getPath();
364   }
365
366   /**
367    * Creates run configuration from right click menu
368    *
369    * @param fixture       test fixture
370    * @param expectedClass expected class of run configuration
371    * @param <C>           expected class of run configuration
372    * @return configuration (if created) or null (otherwise)
373    */
374   @Nullable
375   public static <C extends RunConfiguration> C createRunConfigurationFromContext(
376     @NotNull final CodeInsightTestFixture fixture,
377     @NotNull final Class<C> expectedClass) {
378     final DataContext context = DataManager.getInstance().getDataContext(fixture.getEditor().getComponent());
379     for (final RunConfigurationProducer<?> producer : RunConfigurationProducer.EP_NAME.getExtensions()) {
380       final ConfigurationFromContext fromContext = producer.createConfigurationFromContext(ConfigurationContext.getFromContext(context));
381       if (fromContext == null) {
382         continue;
383       }
384       final C result = PyUtil.as(fromContext.getConfiguration(), expectedClass);
385       if (result != null) {
386         return result;
387       }
388     }
389     return null;
390   }
391
392   /**
393    * Compares sets with string sorting them and displaying one-per-line to make comparision easier
394    *
395    * @param message  message to display in case of error
396    * @param actual   actual set
397    * @param expected expected set
398    */
399   protected static void compareStringSets(@NotNull final String message,
400                                           @NotNull final Set<String> actual,
401                                           @NotNull final Set<String> expected) {
402     final Joiner joiner = Joiner.on("\n");
403     Assert.assertEquals(message, joiner.join(new TreeSet<String>(actual)), joiner.join(new TreeSet<String>(expected)));
404   }
405
406
407   /**
408    * Clicks certain button in document on caret position
409    *
410    * @param action what button to click (const from {@link IdeActions}) (btw, there should be some way to express it using annotations)
411    * @see IdeActions
412    */
413   protected final void pressButton(@NotNull final String action) {
414     CommandProcessor.getInstance().executeCommand(myFixture.getProject(), new Runnable() {
415       @Override
416       public void run() {
417         myFixture.performEditorAction(action);
418       }
419     }, "", null);
420   }
421
422   @NotNull
423   protected CommonCodeStyleSettings getCommonCodeStyleSettings() {
424     return getCodeStyleSettings().getCommonSettings(PythonLanguage.getInstance());
425   }
426
427   @NotNull
428   protected PyCodeStyleSettings getPythonCodeStyleSettings() {
429     return getCodeStyleSettings().getCustomSettings(PyCodeStyleSettings.class);
430   }
431
432   @NotNull
433   protected CodeStyleSettings getCodeStyleSettings() {
434     return CodeStyleSettingsManager.getSettings(myFixture.getProject());
435   }
436
437   @NotNull
438   protected CommonCodeStyleSettings.IndentOptions getIndentOptions() {
439     //noinspection ConstantConditions
440     return getCommonCodeStyleSettings().getIndentOptions();
441   }
442 }
443