use last subtask index instead of subtask number
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / checker / StudyCheckTask.java
1 package com.jetbrains.edu.learning.checker;
2
3 import com.intellij.execution.process.CapturingProcessHandler;
4 import com.intellij.execution.process.ProcessOutput;
5 import com.intellij.ide.projectView.ProjectView;
6 import com.intellij.openapi.application.ApplicationManager;
7 import com.intellij.openapi.diagnostic.Logger;
8 import com.intellij.openapi.editor.Document;
9 import com.intellij.openapi.fileEditor.FileDocumentManager;
10 import com.intellij.openapi.progress.ProgressIndicator;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.ui.MessageType;
13 import com.intellij.openapi.util.Pair;
14 import com.intellij.openapi.util.Ref;
15 import com.intellij.openapi.util.TextRange;
16 import com.intellij.openapi.vfs.VirtualFile;
17 import com.jetbrains.edu.learning.*;
18 import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
19 import com.jetbrains.edu.learning.core.EduNames;
20 import com.jetbrains.edu.learning.core.EduUtils;
21 import com.jetbrains.edu.learning.courseFormat.*;
22 import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
23 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
26
27 import java.util.Map;
28
29 public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable {
30
31   private static final Logger LOG = Logger.getInstance(StudyCheckTask.class);
32   private final Project myProject;
33   protected final StudyState myStudyState;
34   protected final Task myTask;
35   protected final VirtualFile myTaskDir;
36   protected final StudyTaskManager myTaskManger;
37   private final StudyStatus myStatusBeforeCheck;
38   private final Ref<Boolean> myCheckInProcess;
39   private final Process myTestProcess;
40   private final String myCommandLine;
41   private static final String FAILED_CHECK_LAUNCH = "Failed to launch checking";
42
43   public StudyCheckTask(Project project, StudyState studyState, Ref<Boolean> checkInProcess, Process testProcess, String commandLine) {
44     super(project, "Checking Task");
45     myProject = project;
46     myStudyState = studyState;
47     myCheckInProcess = checkInProcess;
48     myTestProcess = testProcess;
49     myCommandLine = commandLine;
50     myTask = studyState.getTask();
51     myTaskDir = studyState.getTaskDir();
52     myTaskManger = StudyTaskManager.getInstance(myProject);
53     myStatusBeforeCheck = myTask.getStatus();
54   }
55
56   @Override
57   public void onSuccess() {
58     StudyUtils.updateToolWindows(myProject);
59     StudyCheckUtils.drawAllPlaceholders(myProject, myTask, myTaskDir);
60     ProjectView.getInstance(myProject).refresh();
61     clearState();
62   }
63
64   protected void clearState() {
65     EduUtils.deleteWindowDescriptions(myTask, myTaskDir);
66     myCheckInProcess.set(false);
67   }
68
69   @Override
70   public void onCancel() {
71     myTask.setStatus(myStatusBeforeCheck);
72     clearState();
73   }
74
75   @Override
76   public void run(@NotNull ProgressIndicator indicator) {
77     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
78     if (course != null) {
79       if (course.isAdaptive()) {
80         checkForAdaptiveCourse(indicator);
81       }
82       else {
83         checkForEduCourse(indicator);
84       }
85     }
86   }
87
88   private void checkForEduCourse(@NotNull ProgressIndicator indicator) {
89     final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator);
90
91     if (testsOutput != null) {
92       if (testsOutput.isSuccess()) {
93         onTaskSolved(testsOutput.getMessage());
94       }
95       else {
96         onTaskFailed(testsOutput.getMessage());
97       }
98       runAfterTaskCheckedActions();
99       final Course course = StudyTaskManager.getInstance(myProject).getCourse();
100       if (course != null && EduNames.STUDY.equals(course.getCourseMode())) {
101         EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), myProject);
102       }
103     }
104   }
105
106   @Nullable
107   private StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull ProgressIndicator indicator) {
108     final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine);
109     final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator);
110     if (indicator.isCanceled()) {
111       ApplicationManager.getApplication().invokeLater(
112         () -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject));
113     }
114
115     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
116     if (course != null) {
117       final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive());
118       String stderr = output.getStderr();
119       if (!stderr.isEmpty() && output.getStdout().isEmpty()) {
120         //log error output of tests
121         LOG.info("#educational " + stderr);
122         return new StudyTestsOutputParser.TestsOutput(false, stderr);
123       }
124       return testsOutput;
125     }
126     return null;
127   }
128
129   private void checkForAdaptiveCourse(ProgressIndicator indicator) {
130     final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator);
131     if (testOutput != null) {
132       // As tests in adaptive courses are created from
133       // samples and stored in task, to disable it we should ignore local testing results
134       if (StudyTaskManager.getInstance(myProject).isEnableTestingFromSamples() && !testOutput.isSuccess()) {
135         onTaskFailed(testOutput.getMessage());
136       }
137       else {
138         final Pair<Boolean, String> pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask);
139         if (pair != null && !(!pair.getFirst() && pair.getSecond().isEmpty())) {
140           if (pair.getFirst()) {
141             onTaskSolved("Congratulations! Remote tests passed.");
142             if (myStatusBeforeCheck != StudyStatus.Solved) {
143               EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, 2, indicator);
144             }
145           }
146           else {
147             final String checkMessage = pair.getSecond();
148             onTaskFailed(checkMessage);
149           }
150           runAfterTaskCheckedActions();
151         }
152         else {
153           ApplicationManager.getApplication().invokeLater(() -> StudyCheckUtils.showTestResultPopUp(FAILED_CHECK_LAUNCH,
154                                                                                                     MessageType.WARNING
155                                                                                                       .getPopupBackground(),
156                                                                                                     myProject));
157         }
158       }
159     }
160   }
161
162   protected void onTaskFailed(String message) {
163     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
164     myTask.setStatus(StudyStatus.Failed);
165     if (course != null) {
166       if (course.isAdaptive()) {
167         ApplicationManager.getApplication().invokeLater(
168           () -> {
169             StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), myProject);
170             StudyCheckUtils.showTestResultsToolWindow(myProject, message, false);
171           });
172       }
173       else {
174         ApplicationManager.getApplication()
175           .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject));
176       }
177     }
178   }
179
180   protected void onTaskSolved(String message) {
181     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
182     myTask.setStatus(StudyStatus.Solved);
183     if (course != null) {
184       if (course.isAdaptive()) {
185         ApplicationManager.getApplication().invokeLater(
186           () -> {
187             StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), myProject);
188             StudyCheckUtils.showTestResultsToolWindow(myProject, message, true);
189           });
190       }
191       else {
192         boolean hasMoreSubtasks = myTask.hasSubtasks() && myTask.getActiveSubtaskIndex() != myTask.getLastSubtaskIndex();
193         int visibleSubtaskIndex = myTask.getActiveSubtaskIndex() + 1;
194         ApplicationManager.getApplication().invokeLater(() -> {
195           int subtaskSize = myTask.getLastSubtaskIndex() + 1;
196           String resultMessage = !hasMoreSubtasks ? message : "Subtask " + visibleSubtaskIndex + "/" +
197                                                               subtaskSize + " solved";
198           StudyCheckUtils.showTestResultPopUp(resultMessage, MessageType.INFO.getPopupBackground(), myProject);
199           if (hasMoreSubtasks) {
200             int nextSubtaskIndex = myTask.getActiveSubtaskIndex() + 1;
201             StudySubtaskUtils.switchStep(myProject, myTask, nextSubtaskIndex);
202             rememberAnswers(nextSubtaskIndex);
203           }
204         });
205       }
206     }
207   }
208
209   private void rememberAnswers(int nextSubtaskIndex) {
210     VirtualFile taskDir = myTask.getTaskDir(myProject);
211     if (taskDir == null) {
212       return;
213     }
214     VirtualFile srcDir = taskDir.findChild(EduNames.SRC);
215     if (srcDir != null) {
216       taskDir = srcDir;
217     }
218     for (Map.Entry<String, TaskFile> entry : myTask.getTaskFiles().entrySet()) {
219       TaskFile taskFile = entry.getValue();
220       VirtualFile virtualFile = taskDir.findChild(entry.getKey());
221       if (virtualFile == null) {
222         continue;
223       }
224       Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
225       if (document == null) {
226         continue;
227       }
228       for (AnswerPlaceholder placeholder : taskFile.getActivePlaceholders()) {
229         if (placeholder.getSubtaskInfos().containsKey(nextSubtaskIndex - 1)) {
230           int offset = placeholder.getOffset();
231           String answer = document.getText(TextRange.create(offset, offset + placeholder.getRealLength()));
232           placeholder.getSubtaskInfos().get(nextSubtaskIndex - 1).setAnswer(answer);
233         }
234       }
235     }
236   }
237
238   private void runAfterTaskCheckedActions() {
239     StudyPluginConfigurator configurator = StudyUtils.getConfigurator(myProject);
240     if (configurator != null) {
241       StudyAfterCheckAction[] checkActions = configurator.getAfterCheckActions();
242       if (checkActions != null) {
243         for (StudyAfterCheckAction action : checkActions) {
244           action.run(myProject, myTask, myStatusBeforeCheck);
245         }
246       }
247     }
248     else {
249       LOG.warn("No configurator is provided for the plugin");
250     }
251   }
252 }