cleanup
[idea/community.git] / java / testFramework / src / com / intellij / codeInsight / JavaCodeInsightTestCase.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInsight;
3
4 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
5 import com.intellij.injected.editor.EditorWindow;
6 import com.intellij.openapi.actionSystem.IdeActions;
7 import com.intellij.openapi.application.ApplicationManager;
8 import com.intellij.openapi.application.WriteAction;
9 import com.intellij.openapi.command.WriteCommandAction;
10 import com.intellij.openapi.command.undo.UndoManager;
11 import com.intellij.openapi.editor.Document;
12 import com.intellij.openapi.editor.Editor;
13 import com.intellij.openapi.editor.EditorFactory;
14 import com.intellij.openapi.editor.LogicalPosition;
15 import com.intellij.openapi.editor.impl.DocumentImpl;
16 import com.intellij.openapi.editor.impl.EditorImpl;
17 import com.intellij.openapi.fileEditor.FileEditorManager;
18 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
19 import com.intellij.openapi.fileEditor.TextEditor;
20 import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
21 import com.intellij.openapi.fileTypes.FileType;
22 import com.intellij.openapi.fileTypes.FileTypeManager;
23 import com.intellij.openapi.roots.ContentEntry;
24 import com.intellij.openapi.roots.LanguageLevelProjectExtension;
25 import com.intellij.openapi.roots.ModifiableRootModel;
26 import com.intellij.openapi.roots.ModuleRootManager;
27 import com.intellij.openapi.util.io.FileUtil;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.openapi.vfs.LocalFileSystem;
30 import com.intellij.openapi.vfs.VfsUtil;
31 import com.intellij.openapi.vfs.VfsUtilCore;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess;
34 import com.intellij.pom.java.LanguageLevel;
35 import com.intellij.psi.PsiClass;
36 import com.intellij.psi.PsiDocumentManager;
37 import com.intellij.psi.PsiFile;
38 import com.intellij.psi.PsiPackage;
39 import com.intellij.psi.impl.source.PostprocessReformattingAspect;
40 import com.intellij.psi.search.ProjectScope;
41 import com.intellij.rt.execution.junit.FileComparisonFailure;
42 import com.intellij.testFramework.*;
43 import com.intellij.util.ArrayUtil;
44 import com.intellij.util.containers.ContainerUtil;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import java.io.File;
49 import java.io.IOException;
50 import java.io.OutputStream;
51 import java.nio.charset.StandardCharsets;
52 import java.util.*;
53
54 public abstract class JavaCodeInsightTestCase extends JavaPsiTestCase {
55   protected Editor myEditor;
56
57   protected Editor createEditor(@NotNull VirtualFile file) {
58     final FileEditorManager instance = FileEditorManager.getInstance(myProject);
59
60     if (file.getFileType().isBinary()) return null;
61     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
62     Editor editor = instance.openTextEditor(new OpenFileDescriptor(myProject, file, 0), false);
63     ((EditorImpl)editor).setCaretActive();
64     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
65     DaemonCodeAnalyzer.getInstance(getProject()).restart();
66
67     return editor;
68   }
69
70   @Override
71   protected void tearDown() throws Exception {
72     try {
73       if (myProject != null) {
74         FileEditorManager editorManager = FileEditorManager.getInstance(myProject);
75         for (VirtualFile openFile : editorManager.getOpenFiles()) {
76           editorManager.closeFile(openFile);
77         }
78       }
79     }
80     catch (Throwable e) {
81       addSuppressedException(e);
82     }
83     finally {
84       myEditor = null;
85       super.tearDown();
86     }
87   }
88
89   @Override
90   protected @NotNull PsiTestData createData() {
91     return new CodeInsightTestData();
92   }
93
94   protected void configureByFile(String filePath) throws Exception {
95     configureByFile(filePath, null);
96   }
97
98   /**
99    * @param files the first file will be loaded in editor
100    */
101   protected VirtualFile configureByFiles(@Nullable String projectRoot, String @NotNull ... files) {
102     if (files.length == 0) return null;
103     final VirtualFile[] vFiles = new VirtualFile[files.length];
104     for (int i = 0; i < files.length; i++) {
105       vFiles[i] = findVirtualFile(files[i]);
106       if (vFiles[i] != null) {
107         VfsTestUtil.assertFilePathEndsWithCaseSensitivePath(vFiles[i], files[i]);
108       }
109     }
110
111     File projectFile = projectRoot == null ? null : new File(getTestDataPath() + projectRoot);
112
113     try {
114       return configureByFiles(projectFile, vFiles);
115     }
116     catch (IOException e) {
117       throw new RuntimeException(e);
118     }
119   }
120
121   protected VirtualFile configureByFile(@NotNull String filePath, @Nullable String projectRoot) throws Exception {
122     VirtualFile vFile = findVirtualFile(filePath);
123     File projectFile = projectRoot == null ? null : new File(getTestDataPath() + projectRoot);
124
125     return configureByFile(vFile, projectFile);
126   }
127
128   protected PsiFile configureByText(@NotNull FileType fileType, @NotNull String text) {
129     return configureByText(fileType, text, null);
130   }
131
132   protected PsiFile configureByText(final @NotNull FileType fileType, @NotNull String text, @Nullable String _extension) {
133     try {
134       final String extension = _extension == null ? fileType.getDefaultExtension() : _extension;
135
136       File dir = createTempDirectory();
137       final File tempFile = FileUtil.createTempFile(dir, "tempFile", "." + extension, true);
138       final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
139       if (fileTypeManager.getFileTypeByExtension(extension) != fileType) {
140         WriteCommandAction.writeCommandAction(getProject()).run(() -> fileTypeManager.associateExtension(fileType, extension));
141       }
142       final VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempFile);
143       assert vFile != null;
144       WriteAction.runAndWait(() -> {
145         vFile.setCharset(StandardCharsets.UTF_8);
146         VfsUtil.saveText(vFile, text);
147       });
148
149       final VirtualFile vdir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(dir);
150
151       PsiTestUtil.addSourceRoot(myModule, vdir);
152
153       configureByExistingFile(vFile);
154
155       assertEquals(fileType, myFile.getVirtualFile().getFileType());
156       return myFile;
157     }
158     catch (IOException e) {
159       throw new RuntimeException(e);
160     }
161   }
162
163
164   protected void configureByFile(@NotNull VirtualFile vFile) throws IOException {
165     configureByFile(vFile, null);
166   }
167
168   protected void configureByExistingFile(final @NotNull VirtualFile virtualFile) {
169     myFile = null;
170     myEditor = null;
171
172     final Editor editor = createEditor(virtualFile);
173
174     final Document document = editor.getDocument();
175     final EditorInfo editorInfo = new EditorInfo(document.getText());
176
177     final String newFileText = editorInfo.getNewFileText();
178     ApplicationManager.getApplication().runWriteAction(() -> {
179       if (!document.getText().equals(newFileText)) {
180         document.setText(newFileText);
181       }
182
183       PsiFile file = myPsiManager.findFile(virtualFile);
184       if (myFile == null) myFile = file;
185
186       if (myEditor == null) myEditor = editor;
187
188       editorInfo.applyToEditor(editor);
189     });
190
191
192     PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
193   }
194
195   public VirtualFile doConfigureByFiles(final @Nullable File rawProjectRoot, final VirtualFile @NotNull ... vFiles) throws IOException {
196     return configureByFiles(rawProjectRoot, vFiles);
197   }
198
199   protected VirtualFile configureByFiles(final @Nullable File rawProjectRoot, final VirtualFile @NotNull ... vFiles) throws IOException {
200     myFile = null;
201     myEditor = null;
202
203     final File toDirIO = createTempDirectory();
204     final VirtualFile toDir = getVirtualFile(toDirIO);
205
206     ApplicationManager.getApplication().runWriteAction(() -> {
207       try {
208         final ModuleRootManager rootManager = ModuleRootManager.getInstance(myModule);
209         final ModifiableRootModel rootModel = rootManager.getModifiableModel();
210         if (clearModelBeforeConfiguring()) {
211           rootModel.clear();
212         }
213
214         // auxiliary files should be copied first
215         VirtualFile[] reversed = ArrayUtil.reverseArray(vFiles);
216         Map<VirtualFile, EditorInfo> editorInfos;
217         if (rawProjectRoot != null) {
218           final File projectRoot = rawProjectRoot.getCanonicalFile();
219           FileUtil.copyDir(projectRoot, toDirIO);
220           VirtualFile fromDir = getVirtualFile(projectRoot);
221           editorInfos =
222             copyFilesFillingEditorInfos(fromDir, toDir, ContainerUtil.map2Array(reversed, String.class, s -> s.getPath().substring(projectRoot.getPath().length())));
223
224           toDir.refresh(false, true);
225         }
226         else {
227           editorInfos = new LinkedHashMap<>();
228           for (final VirtualFile vFile : reversed) {
229             VirtualFile parent = vFile.getParent();
230             assert parent.isDirectory() : parent;
231             editorInfos.putAll(copyFilesFillingEditorInfos(parent, toDir, vFile.getName()));
232           }
233         }
234
235         boolean sourceRootAdded = false;
236         if (isAddDirToContentRoot()) {
237           final ContentEntry contentEntry = rootModel.addContentEntry(toDir);
238           if (isAddDirToSource()) {
239             sourceRootAdded = true;
240             contentEntry.addSourceFolder(toDir, isAddDirToTests());
241           }
242         }
243         doCommitModel(rootModel);
244         if (sourceRootAdded) {
245           sourceRootAdded(toDir);
246         }
247
248         openEditorsAndActivateLast(editorInfos);
249       }
250       catch (IOException e) {
251         LOG.error(e);
252       }
253     });
254
255
256     return toDir;
257   }
258
259   protected boolean isAddDirToTests() {
260     return false;
261   }
262
263   protected void doCommitModel(@NotNull ModifiableRootModel rootModel) {
264     rootModel.commit();
265   }
266
267   protected void sourceRootAdded(final VirtualFile dir) {
268   }
269
270   protected @NotNull Map<VirtualFile, EditorInfo> copyFilesFillingEditorInfos(@NotNull String testDataFromDir,
271                                                                               @NotNull VirtualFile toDir,
272                                                                               String @NotNull ... relativePaths) throws IOException {
273     if (!testDataFromDir.startsWith("/")) testDataFromDir = "/" + testDataFromDir;
274     return copyFilesFillingEditorInfos(LocalFileSystem.getInstance().refreshAndFindFileByPath(getTestDataPath() + testDataFromDir), toDir, relativePaths);
275   }
276
277   protected @NotNull Map<VirtualFile, EditorInfo> copyFilesFillingEditorInfos(@NotNull VirtualFile fromDir,
278                                                                               @NotNull VirtualFile toDir,
279                                                                               String @NotNull ... relativePaths) throws IOException {
280     Map<VirtualFile, EditorInfo> editorInfos = new LinkedHashMap<>();
281
282     List<OutputStream> streamsToClose = new ArrayList<>();
283
284     for (String relativePath : relativePaths) {
285       relativePath = StringUtil.trimStart(relativePath, "/");
286       final VirtualFile fromFile = fromDir.findFileByRelativePath(relativePath);
287       assertNotNull(fromDir.getPath() + "/" + relativePath, fromFile);
288       VirtualFile toFile = toDir.findFileByRelativePath(relativePath);
289       if (toFile == null) {
290         final File file = new File(toDir.getPath(), relativePath);
291         FileUtil.createIfDoesntExist(file);
292         toFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
293         assertNotNull(file.getCanonicalPath(), toFile);
294       }
295       toFile.putUserData(VfsTestUtil.TEST_DATA_FILE_PATH, FileUtil.toSystemDependentName(fromFile.getPath()));
296       editorInfos.put(toFile, copyContent(fromFile, toFile, streamsToClose));
297     }
298
299     for(int i = streamsToClose.size() -1; i >= 0 ; --i) {
300       streamsToClose.get(i).close();
301     }
302     return editorInfos;
303   }
304
305   private EditorInfo copyContent(@NotNull VirtualFile from, @NotNull VirtualFile to, @NotNull List<? super OutputStream> streamsToClose) throws IOException {
306     byte[] content = from.getFileType().isBinary() ? from.contentsToByteArray(): null;
307     final String fileText = from.getFileType().isBinary() ? null : StringUtil.convertLineSeparators(VfsUtilCore.loadText(from));
308
309     EditorInfo editorInfo = fileText == null ? null : new EditorInfo(fileText);
310     String newFileText = fileText == null ? null : editorInfo.getNewFileText();
311     doWrite(newFileText, to, content, streamsToClose);
312     return editorInfo;
313   }
314
315   protected final void setActiveEditor(@NotNull Editor editor) {
316     myEditor = editor;
317     myFile = getPsiFile(editor.getDocument());
318   }
319
320   protected @NotNull List<Editor> openEditorsAndActivateLast(@NotNull Map<VirtualFile, EditorInfo> editorInfos) {
321     final List<Editor> list = openEditors(editorInfos);
322     setActiveEditor(list.get(list.size() - 1));
323     return list;
324   }
325
326   protected final @NotNull List<Editor> openEditors(@NotNull Map<VirtualFile, EditorInfo> editorInfos) {
327     return ContainerUtil.map(editorInfos.keySet(), newVFile -> {
328       PsiFile file = myPsiManager.findFile(newVFile);
329       if (myFile == null) myFile = file;
330
331       Editor editor = createEditor(newVFile);
332       if (myEditor == null) myEditor = editor;
333
334       EditorInfo editorInfo = editorInfos.get(newVFile);
335       if (editorInfo != null) {
336         editorInfo.applyToEditor(editor);
337       }
338       return editor;
339     });
340   }
341
342   private void doWrite(final String newFileText,
343                        @NotNull VirtualFile newVFile,
344                        byte[] content,
345                        @NotNull List<? super OutputStream> streamsToClose) throws IOException {
346     if (newFileText == null) {
347       final OutputStream outputStream = newVFile.getOutputStream(this, -1, -1);
348       outputStream.write(content);
349       streamsToClose.add(outputStream);
350     }
351     else {
352       setFileText(newVFile, newFileText);
353     }
354   }
355
356   protected boolean isAddDirToContentRoot() {
357     return true;
358   }
359
360   protected boolean isAddDirToSource() {
361     return true;
362   }
363
364   protected VirtualFile configureByFile(@NotNull VirtualFile vFile, File projectRoot) throws IOException {
365     return configureByFiles(projectRoot, vFile);
366   }
367
368   protected boolean clearModelBeforeConfiguring() {
369     return false;
370   }
371
372   protected void setupCursorAndSelection(final @NotNull Editor editor) {
373     Document document = editor.getDocument();
374     EditorTestUtil.CaretAndSelectionState caretState = EditorTestUtil.extractCaretAndSelectionMarkers(document);
375     EditorTestUtil.setCaretsAndSelection(editor, caretState);
376     PsiDocumentManager.getInstance(myProject).commitAllDocuments();
377   }
378
379   @Override
380   protected void configure(@NotNull String path, String dataName) throws Exception {
381     super.configure(path, dataName);
382
383     myEditor = createEditor(myFile.getVirtualFile());
384
385     CodeInsightTestData data = (CodeInsightTestData) myTestDataBefore;
386
387     LogicalPosition pos = new LogicalPosition(data.getLineNumber() - 1, data.getColumnNumber() - 1);
388     myEditor.getCaretModel().moveToLogicalPosition(pos);
389
390     int selectionEnd;
391     int selectionStart = selectionEnd = myEditor.getCaretModel().getOffset();
392
393     if (data.getSelectionStartColumnNumber() >= 0) {
394       selectionStart = myEditor.logicalPositionToOffset(new LogicalPosition(data.getSelectionEndLineNumber() - 1, data.getSelectionStartColumnNumber() - 1));
395       selectionEnd = myEditor.logicalPositionToOffset(new LogicalPosition(data.getSelectionEndLineNumber() - 1, data.getSelectionEndColumnNumber() - 1));
396     }
397
398     myEditor.getSelectionModel().setSelection(selectionStart, selectionEnd);
399   }
400
401   protected void checkResultByFile(@NotNull String filePath) throws Exception {
402     checkResultByFile(filePath, false);
403   }
404
405   protected void checkResultByFile(final @NotNull String filePath, final boolean stripTrailingSpaces) throws Exception {
406     WriteCommandAction.writeCommandAction(getProject()).run(() -> {
407       PostprocessReformattingAspect.getInstance(getProject()).doPostponedFormatting();
408       if (stripTrailingSpaces) {
409         ((DocumentImpl)myEditor.getDocument()).stripTrailingSpaces(getProject());
410       }
411
412       PsiDocumentManager.getInstance(myProject).commitAllDocuments();
413
414       VirtualFile vFile = findVirtualFile(filePath);
415
416       VfsTestUtil.assertFilePathEndsWithCaseSensitivePath(vFile, filePath);
417       String expectedText;
418       try {
419         expectedText = VfsUtilCore.loadText(vFile);
420       }
421       catch (IOException e) {
422         throw new RuntimeException(e);
423       }
424
425       expectedText = StringUtil.convertLineSeparators(expectedText);
426       Document document = EditorFactory.getInstance().createDocument(expectedText);
427
428       EditorTestUtil.CaretAndSelectionState caretState = EditorTestUtil.extractCaretAndSelectionMarkers(document);
429
430       expectedText = document.getText();
431       if (stripTrailingSpaces) {
432         Document document1 = EditorFactory.getInstance().createDocument(expectedText);
433         ((DocumentImpl)document1).stripTrailingSpaces(getProject());
434         expectedText = document1.getText();
435       }
436
437       if (myEditor instanceof EditorWindow) {
438         myEditor = ((EditorWindow)myEditor).getDelegate();
439       }
440       myFile = PsiDocumentManager.getInstance(getProject()).getPsiFile(myEditor.getDocument());
441
442       String actualText = StringUtil.convertLineSeparators(myFile.getText());
443
444       if (!Objects.equals(expectedText, actualText)) {
445         throw new FileComparisonFailure("Text mismatch in file " + filePath, expectedText, actualText, vFile.getPath());
446       }
447
448       EditorTestUtil.verifyCaretAndSelectionState(myEditor, caretState);
449     });
450   }
451
452   @Override
453   protected void checkResult(String dataName) throws Exception {
454     PsiDocumentManager.getInstance(myProject).commitAllDocuments();
455     super.checkResult(dataName);
456
457     CodeInsightTestData data = (CodeInsightTestData) myTestDataAfter;
458
459     if (data.getColumnNumber() >= 0) {
460       assertEquals(dataName + ":caretColumn", data.getColumnNumber(), myEditor.getCaretModel().getLogicalPosition().column + 1);
461     }
462     if (data.getLineNumber() >= 0) {
463       assertEquals(dataName + ":caretLine", data.getLineNumber(), myEditor.getCaretModel().getLogicalPosition().line + 1);
464     }
465
466     int selectionStart = myEditor.getSelectionModel().getSelectionStart();
467     int selectionEnd = myEditor.getSelectionModel().getSelectionEnd();
468     LogicalPosition startPosition = myEditor.offsetToLogicalPosition(selectionStart);
469     LogicalPosition endPosition = myEditor.offsetToLogicalPosition(selectionEnd);
470
471     if (data.getSelectionStartColumnNumber() >= 0) {
472       assertEquals(dataName + ":selectionStartColumn", data.getSelectionStartColumnNumber(), startPosition.column + 1);
473     }
474     if (data.getSelectionStartLineNumber() >= 0) {
475       assertEquals(dataName + ":selectionStartLine", data.getSelectionStartLineNumber(), startPosition.line + 1);
476     }
477     if (data.getSelectionEndColumnNumber() >= 0) {
478       assertEquals(dataName + ":selectionEndColumn", data.getSelectionEndColumnNumber(), endPosition.column + 1);
479     }
480     if (data.getSelectionEndLineNumber() >= 0) {
481       assertEquals(dataName + ":selectionEndLine", data.getSelectionEndLineNumber(), endPosition.line + 1);
482     }
483   }
484
485   protected @NotNull VirtualFile getVirtualFile(@NotNull String filePath) {
486     return findVirtualFile(filePath);
487   }
488
489   protected @NotNull VirtualFile findVirtualFile(@NotNull String filePath) {
490     String absolutePath = getTestDataPath() + filePath;
491     VfsRootAccess.allowRootAccess(getTestRootDisposable(), absolutePath);
492     return VfsTestUtil.findFileByCaseSensitivePath(absolutePath);
493   }
494
495   protected @NotNull String getTestRoot(){
496     return FileUtil.toSystemIndependentName(getTestDataPath());
497   }
498
499   public Editor getEditor() {
500     return myEditor;
501   }
502
503   protected void type(char c) {
504     LightPlatformCodeInsightTestCase.type(c, getEditor(),getProject());
505   }
506
507   protected void undo() {
508     UndoManager undoManager = UndoManager.getInstance(myProject);
509     TextEditor textEditor = TextEditorProvider.getInstance().getTextEditor(getEditor());
510     undoManager.undo(textEditor);
511   }
512
513   protected void caretLeft() {
514     caretLeft(getEditor());
515   }
516   protected void caretLeft(@NotNull Editor editor) {
517     LightPlatformCodeInsightTestCase.executeAction(IdeActions.ACTION_EDITOR_MOVE_CARET_LEFT, editor, getProject());
518   }
519   protected void caretRight() {
520     caretRight(getEditor());
521   }
522   protected void caretRight(@NotNull Editor editor) {
523     LightPlatformCodeInsightTestCase.executeAction(IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT, editor, getProject());
524   }
525   protected void caretUp() {
526     LightPlatformCodeInsightTestCase.executeAction(IdeActions.ACTION_EDITOR_MOVE_CARET_UP, myEditor, getProject());
527   }
528
529   protected void deleteLine() {
530     LightPlatformCodeInsightTestCase.deleteLine(myEditor,getProject());
531   }
532
533   protected void type(@NotNull String s) {
534     for (char c : s.toCharArray()) {
535       type(c);
536     }
537   }
538
539   protected void backspace() {
540     backspace(getEditor());
541   }
542
543   protected void backspace(final @NotNull Editor editor) {
544     LightPlatformCodeInsightTestCase.backspace(editor,getProject());
545   }
546
547   protected void ctrlW() {
548     LightPlatformCodeInsightTestCase.ctrlW(getEditor(),getProject());
549   }
550
551   protected void ctrlD() {
552     LightPlatformCodeInsightTestCase.ctrlD(getEditor(),getProject());
553   }
554
555   protected void delete(final @NotNull Editor editor) {
556     LightPlatformCodeInsightTestCase.delete(editor, getProject());
557   }
558
559   protected @NotNull PsiClass findClass(final @NotNull String name) {
560     final PsiClass aClass = myJavaFacade.findClass(name, ProjectScope.getProjectScope(getProject()));
561     assertNotNull("Class " + name + " not found", aClass);
562     return aClass;
563   }
564
565   protected @NotNull PsiPackage findPackage(final @NotNull String name) {
566     final PsiPackage aPackage = myJavaFacade.findPackage(name);
567     assertNotNull("Package " + name + " not found", aPackage);
568     return aPackage;
569   }
570
571   protected void setLanguageLevel(@NotNull LanguageLevel level) {
572     LanguageLevelProjectExtension.getInstance(getProject()).setLanguageLevel(level);
573   }
574 }