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