e8ea54843535ec08343cdcd6ba673e9129a1cf4c
[idea/community.git] / python / educational / course-creator / src / com / jetbrains / edu / coursecreator / actions / CCRunTestsAction.java
1 /*
2  * Copyright 2000-2014 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.edu.coursecreator.actions;
17
18 import com.intellij.execution.Location;
19 import com.intellij.execution.actions.ConfigurationContext;
20 import com.intellij.icons.AllIcons;
21 import com.intellij.openapi.actionSystem.AnAction;
22 import com.intellij.openapi.actionSystem.AnActionEvent;
23 import com.intellij.openapi.actionSystem.Presentation;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.fileEditor.FileDocumentManager;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.util.TextRange;
30 import com.intellij.openapi.util.io.FileUtil;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.psi.PsiDirectory;
33 import com.intellij.psi.PsiElement;
34 import com.intellij.psi.PsiFile;
35 import com.intellij.util.containers.HashMap;
36 import com.jetbrains.edu.coursecreator.CCProjectService;
37 import com.jetbrains.edu.coursecreator.format.*;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.io.PrintWriter;
44 import java.util.Map;
45
46 public abstract class CCRunTestsAction extends AnAction {
47   private static final Logger LOG = Logger.getInstance(CCRunTestsAction.class.getName());
48
49   public CCRunTestsAction() {
50     getTemplatePresentation().setIcon(AllIcons.Actions.Lightning);
51   }
52
53   @Override
54   public void update(@NotNull AnActionEvent e) {
55     Presentation presentation = e.getPresentation();
56     presentation.setText("");
57     presentation.setVisible(false);
58     presentation.setEnabled(false);
59
60     final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
61     Location location = context.getLocation();
62     if (location == null) {
63       return;
64     }
65     PsiElement psiElement = location.getPsiElement();
66     PsiFile psiFile = psiElement.getContainingFile();
67     Project project = e.getProject();
68     if (project == null || psiFile == null) {
69       presentation.setVisible(false);
70       presentation.setEnabled(false);
71       return;
72     }
73     final CCProjectService service = CCProjectService.getInstance(project);
74     final Course course = service.getCourse();
75     final PsiDirectory taskDir = psiFile.getContainingDirectory();
76     final PsiDirectory lessonDir = taskDir.getParent();
77     if (lessonDir == null) return;
78     if (course == null) return;
79     final Lesson lesson = course.getLesson(lessonDir.getName());
80     if (lesson == null) return;
81     final Task task = lesson.getTask(taskDir.getName());
82     if (task == null) {
83       presentation.setVisible(false);
84       presentation.setEnabled(false);
85       return;
86     }
87     TaskFile taskFile = task.getTaskFile(psiFile.getName());
88     if (taskFile == null) {
89       LOG.info("could not find task file");
90       presentation.setVisible(false);
91       presentation.setEnabled(false);
92       return;
93     }
94     if (psiFile.getName().contains(".answer")) {
95       presentation.setEnabled(true);
96       presentation.setVisible(true);
97       presentation.setText("Run tests from '" + psiFile.getName() + "'");
98     }
99     else {
100       presentation.setEnabled(false);
101       presentation.setVisible(false);
102     }
103   }
104
105   public void actionPerformed(@NotNull AnActionEvent e) {
106     final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
107     run(context);
108   }
109
110   private void run(final @NotNull ConfigurationContext context) {
111     ApplicationManager.getApplication().runWriteAction(new Runnable() {
112       @Override
113       public void run() {
114         final Project project = context.getProject();
115         PsiElement location = context.getPsiLocation();
116         final Course course = CCProjectService.getInstance(project).getCourse();
117         if (course == null || location == null) {
118           return;
119         }
120         PsiFile psiFile = location.getContainingFile();
121         final VirtualFile virtualFile = psiFile.getVirtualFile();
122         final VirtualFile taskDir = virtualFile.getParent();
123         if (taskDir == null) {
124           return;
125         }
126         final Task task = getTask(course, taskDir);
127         if (task == null) {
128           return;
129         }
130         clearTestEnvironment(taskDir, project);
131         for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
132           final String name = entry.getKey();
133           createTestEnvironment(taskDir, name, entry.getValue(), project);
134           VirtualFile testFile = taskDir.findChild("tests.py");
135           if (testFile == null) {
136             return;
137           }
138           executeTests(project, virtualFile, taskDir, testFile);
139         }
140       }
141     });
142   }
143
144   private static void createTestEnvironment(@NotNull final VirtualFile taskDir, final String fileName, @NotNull final TaskFile taskFile,
145                                             @NotNull final Project project) {
146     try {
147       String answerFileName = FileUtil.getNameWithoutExtension(fileName) + ".answer";
148       final VirtualFile answerFile = taskDir.findChild(answerFileName);
149       if (answerFile == null) {
150         LOG.debug("could not find answer file " + answerFileName);
151         return;
152       }
153       ApplicationManager.getApplication().runWriteAction(new Runnable() {
154         @Override
155         public void run() {
156           final FileDocumentManager documentManager = FileDocumentManager.getInstance();
157           documentManager.saveAllDocuments();
158         }
159       });
160       answerFile.copy(project, taskDir, fileName);
161       flushWindows(taskFile, answerFile);
162       createResourceFiles(answerFile, project);
163     }
164     catch (IOException e) {
165       LOG.error(e);
166     }
167   }
168
169   public static void clearTestEnvironment(@NotNull final VirtualFile taskDir, @NotNull final Project project) {
170     try {
171       VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
172       if (ideaDir == null) {
173         LOG.debug("idea directory doesn't exist");
174         return;
175       }
176       VirtualFile courseResourceDir = ideaDir.findChild("course");
177       if (courseResourceDir != null) {
178         courseResourceDir.delete(project);
179       }
180       VirtualFile[] taskDirChildren = taskDir.getChildren();
181       for (VirtualFile file : taskDirChildren) {
182         if (file.getName().contains("_windows")) {
183           file.delete(project);
184         }
185         if (CCProjectService.getInstance(project).isTaskFile(file)) {
186           file.delete(project);
187         }
188       }
189     }
190     catch (IOException e) {
191       LOG.error(e);
192     }
193   }
194
195   protected abstract void executeTests(@NotNull final Project project,
196                                    @NotNull final VirtualFile virtualFile,
197                                    @NotNull final VirtualFile taskDir,
198                                    @NotNull final VirtualFile testFile);
199
200   @Nullable
201   private static Task getTask(@NotNull final Course course, @NotNull final VirtualFile taskDir) {
202     if (!taskDir.getName().contains("task")) {
203       return null;
204     }
205     VirtualFile lessonDir = taskDir.getParent();
206     if (lessonDir == null || !lessonDir.getName().contains("lesson")) {
207       return null;
208     }
209     Lesson lesson = course.getLesson(lessonDir.getName());
210     if (lesson == null) {
211       return null;
212     }
213     return lesson.getTask(taskDir.getName());
214   }
215
216
217   //some tests could compare task files after user modifications with initial task files
218   private static void createResourceFiles(@NotNull final VirtualFile file, @NotNull final Project project) {
219     VirtualFile taskDir = file.getParent();
220     int index = CCProjectService.getIndex(taskDir.getName(), "task");
221     VirtualFile lessonDir = taskDir.getParent();
222     int lessonIndex = CCProjectService.getIndex(lessonDir.getName(), "lesson");
223     Course course = CCProjectService.getInstance(project).getCourse();
224     if (course == null) {
225       return;
226     }
227     VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
228     assert ideaDir != null;
229     try {
230       VirtualFile courseResourceDir = findOrCreateDir(project, ideaDir, "course");
231       VirtualFile lessonResourceDir = findOrCreateDir(project, courseResourceDir, lessonDir.getName());
232       VirtualFile taskResourceDir = findOrCreateDir(project, lessonResourceDir, taskDir.getName());
233       if (CCProjectService.indexIsValid(lessonIndex, course.getLessons())) {
234         Lesson lesson = course.getLessons().get(lessonIndex);
235         if (CCProjectService.indexIsValid(index, lesson.getTaskList())) {
236           Task task = lesson.getTaskList().get(index);
237           HashMap<TaskFile, TaskFile> taskFilesCopy = new HashMap<TaskFile, TaskFile>();
238           for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
239             CCCreateCourseArchive.createUserFile(project, taskFilesCopy, taskResourceDir, taskDir, entry);
240             CCCreateCourseArchive.resetTaskFiles(taskFilesCopy);
241           }
242         }
243       }
244     }
245     catch (IOException e) {
246       LOG.error(e);
247     }
248   }
249
250   private static VirtualFile findOrCreateDir(@NotNull final Project project, @NotNull final VirtualFile dir, String name) throws IOException {
251     VirtualFile targetDir = dir.findChild(name);
252     if (targetDir == null) {
253       targetDir = dir.createChildDirectory(project, name);
254     }
255     return targetDir;
256   }
257
258   @SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
259   private static void flushWindows(TaskFile taskFile, VirtualFile file) {
260     VirtualFile taskDir = file.getParent();
261     final Document document = FileDocumentManager.getInstance().getDocument(file);
262     if (document == null) {
263       LOG.debug("Couldn't flush windows");
264       return;
265     }
266     if (taskDir != null) {
267       String name = file.getNameWithoutExtension() + "_windows";
268       PrintWriter printWriter = null;
269       try {
270         final VirtualFile windowsFile = taskDir.createChildData(taskFile, name);
271         printWriter = new PrintWriter(new FileOutputStream(windowsFile.getPath()));
272         for (AnswerPlaceholder answerPlaceholder : taskFile.getTaskWindows()) {
273           int start = answerPlaceholder.getRealStartOffset(document);
274           String windowDescription = document.getText(new TextRange(start, start + answerPlaceholder.getReplacementLength()));
275           printWriter.println("#educational_plugin_window = " + windowDescription);
276         }
277         ApplicationManager.getApplication().runWriteAction(new Runnable() {
278           @Override
279           public void run() {
280             FileDocumentManager.getInstance().saveDocument(document);
281           }
282         });
283       }
284       catch (IOException e) {
285         LOG.error(e);
286       }
287       finally {
288         if (printWriter != null) {
289           printWriter.close();
290         }
291       }
292     }
293   }
294 }