switch to the next subtask when prev is solved
[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.progress.ProgressIndicator;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.openapi.ui.MessageType;
11 import com.intellij.openapi.util.Pair;
12 import com.intellij.openapi.util.Ref;
13 import com.intellij.openapi.vfs.VirtualFile;
14 import com.jetbrains.edu.learning.*;
15 import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
16 import com.jetbrains.edu.learning.core.EduNames;
17 import com.jetbrains.edu.learning.core.EduUtils;
18 import com.jetbrains.edu.learning.courseFormat.Course;
19 import com.jetbrains.edu.learning.courseFormat.StudyStatus;
20 import com.jetbrains.edu.learning.courseFormat.Task;
21 import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector;
22 import com.jetbrains.edu.learning.stepic.EduStepicConnector;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable {
27
28   private static final Logger LOG = Logger.getInstance(StudyCheckTask.class);
29   private final Project myProject;
30   protected final StudyState myStudyState;
31   protected final Task myTask;
32   protected final VirtualFile myTaskDir;
33   protected final StudyTaskManager myTaskManger;
34   private final StudyStatus myStatusBeforeCheck;
35   private final Ref<Boolean> myCheckInProcess;
36   private final Process myTestProcess;
37   private final String myCommandLine;
38   private static final String FAILED_CHECK_LAUNCH = "Failed to launch checking";
39
40   public StudyCheckTask(Project project, StudyState studyState, Ref<Boolean> checkInProcess, Process testProcess, String commandLine) {
41     super(project, "Checking Task");
42     myProject = project;
43     myStudyState = studyState;
44     myCheckInProcess = checkInProcess;
45     myTestProcess = testProcess;
46     myCommandLine = commandLine;
47     myTask = studyState.getTask();
48     myTaskDir = studyState.getTaskDir();
49     myTaskManger = StudyTaskManager.getInstance(myProject);
50     myStatusBeforeCheck = myTask.getStatus();
51   }
52
53   @Override
54   public void onSuccess() {
55     StudyUtils.updateToolWindows(myProject);
56     StudyCheckUtils.drawAllPlaceholders(myProject, myTask, myTaskDir);
57     ProjectView.getInstance(myProject).refresh();
58     clearState();
59   }
60
61   protected void clearState() {
62     EduUtils.deleteWindowDescriptions(myTask, myTaskDir);
63     myCheckInProcess.set(false);
64   }
65
66   @Override
67   public void onCancel() {
68     myTask.setStatus(myStatusBeforeCheck);
69     clearState();
70   }
71
72   @Override
73   public void run(@NotNull ProgressIndicator indicator) {
74     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
75     if (course != null) {
76       if (course.isAdaptive()) {
77         checkForAdaptiveCourse(indicator);
78       }
79       else {
80         checkForEduCourse(indicator);
81       }
82     }
83   }
84
85   private void checkForEduCourse(@NotNull ProgressIndicator indicator) {
86     final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator);
87
88     if (testsOutput != null) {
89       if (testsOutput.isSuccess()) {
90         onTaskSolved(testsOutput.getMessage());
91       }
92       else {
93         onTaskFailed(testsOutput.getMessage());
94       }
95       runAfterTaskCheckedActions();
96       final Course course = StudyTaskManager.getInstance(myProject).getCourse();
97       if (course != null && EduNames.STUDY.equals(course.getCourseMode())) {
98         EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), myProject);
99       }
100     }
101   }
102
103   @Nullable
104   private StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull ProgressIndicator indicator) {
105     final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine);
106     final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator);
107     if (indicator.isCanceled()) {
108       ApplicationManager.getApplication().invokeLater(
109         () -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject));
110     }
111
112     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
113     if (course != null) {
114       final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive());
115       String stderr = output.getStderr();
116       if (!stderr.isEmpty() && output.getStdout().isEmpty()) {
117         //log error output of tests
118         LOG.info("#educational " + stderr);
119         return new StudyTestsOutputParser.TestsOutput(false, stderr);
120       }
121       return testsOutput;
122     }
123     return null;
124   }
125
126   private void checkForAdaptiveCourse(ProgressIndicator indicator) {
127     final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator);
128     if (testOutput != null) {
129       // As tests in adaptive courses are created from
130       // samples and stored in task, to disable it we should ignore local testing results
131       if (StudyTaskManager.getInstance(myProject).isEnableTestingFromSamples() && !testOutput.isSuccess()) {
132         onTaskFailed(testOutput.getMessage());
133       }
134       else {
135         final Pair<Boolean, String> pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask);
136         if (pair != null && !(!pair.getFirst() && pair.getSecond().isEmpty())) {
137           if (pair.getFirst()) {
138             onTaskSolved("Congratulations! Remote tests passed.");
139             if (myStatusBeforeCheck != StudyStatus.Solved) {
140               EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, 2, indicator);
141             }
142           }
143           else {
144             final String checkMessage = pair.getSecond();
145             onTaskFailed(checkMessage);
146           }
147           runAfterTaskCheckedActions();
148         }
149         else {
150           ApplicationManager.getApplication().invokeLater(() -> StudyCheckUtils.showTestResultPopUp(FAILED_CHECK_LAUNCH,
151                                                                                                     MessageType.WARNING
152                                                                                                       .getPopupBackground(),
153                                                                                                     myProject));
154         }
155       }
156     }
157   }
158
159   protected void onTaskFailed(String message) {
160     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
161     myTask.setStatus(StudyStatus.Failed);
162     if (course != null) {
163       if (course.isAdaptive()) {
164         ApplicationManager.getApplication().invokeLater(
165           () -> {
166             StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), myProject);
167             StudyCheckUtils.showTestResultsToolWindow(myProject, message, false);
168           });
169       }
170       else {
171         ApplicationManager.getApplication()
172           .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject));
173       }
174     }
175   }
176
177   protected void onTaskSolved(String message) {
178     final Course course = StudyTaskManager.getInstance(myProject).getCourse();
179     myTask.setStatus(StudyStatus.Solved);
180     if (course != null) {
181       if (course.isAdaptive()) {
182         ApplicationManager.getApplication().invokeLater(
183           () -> {
184             StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), myProject);
185             StudyCheckUtils.showTestResultsToolWindow(myProject, message, true);
186           });
187       }
188       else {
189         boolean hasMoreSubtasks = myTask.hasSubtasks() && myTask.getActiveSubtaskIndex() != myTask.getSubtaskNum() - 1;
190         int visibleSubtaskIndex = myTask.getActiveSubtaskIndex() + 1;
191         ApplicationManager.getApplication().invokeLater(() -> {
192           String resultMessage = !hasMoreSubtasks ? message : "Subtask " + visibleSubtaskIndex + "/" + myTask.getSubtaskNum() + " solved";
193           StudyCheckUtils.showTestResultPopUp(resultMessage, MessageType.INFO.getPopupBackground(), myProject);
194           if (hasMoreSubtasks) {
195             StudySubtaskUtils.switchStep(myProject, myTask, myTask.getActiveSubtaskIndex() + 1);
196           }
197         });
198       }
199     }
200   }
201
202   private void runAfterTaskCheckedActions() {
203     StudyPluginConfigurator configurator = StudyUtils.getConfigurator(myProject);
204     if (configurator != null) {
205       StudyAfterCheckAction[] checkActions = configurator.getAfterCheckActions();
206       if (checkActions != null) {
207         for (StudyAfterCheckAction action : checkActions) {
208           action.run(myProject, myTask, myStatusBeforeCheck);
209         }
210       }
211     }
212     else {
213       LOG.warn("No configurator is provided for the plugin");
214     }
215   }
216 }