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