From d996b96ea13f5c4839b91210bc02c72173e10c7e Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Tue, 11 Apr 2017 17:43:28 +0300 Subject: [PATCH] check for PyCharm tasks (EDU-882) --- .../edu/learning/EduPluginConfigurator.java | 8 +- .../learning/actions/StudyCheckAction.java | 5 +- .../edu/learning/checker/StudyCheckTask.java | 201 ------------------ .../edu/learning/checker/StudyCheckUtils.java | 17 ++ .../edu/learning/checker/StudyTestRunner.java | 3 + .../courseFormat/tasks/PyCharmTask.java | 11 + .../courseFormat/tasks/TheoryTask.java | 2 +- .../Edu-Python/resources/META-INF/plugin.xml | 1 - .../edu/learning/PyEduPluginConfigurator.java | 8 +- .../edu/learning/PyStudyCheckAction.java | 126 ----------- .../learning/PyStudyInstructionPainter.java | 2 +- .../edu/learning/PyStudyTaskChecker.java | 114 ++++++++++ .../edu/learning/PyStudyTestRunner.java | 20 +- 13 files changed, 174 insertions(+), 344 deletions(-) delete mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java delete mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java create mode 100644 python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTaskChecker.java diff --git a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java index 0bc4152966a2..2dc554e97479 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/EduPluginConfigurator.java @@ -15,9 +15,11 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.jetbrains.edu.learning.actions.*; +import com.jetbrains.edu.learning.checker.StudyTaskChecker; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; @@ -93,13 +95,13 @@ public interface EduPluginConfigurator { @NotNull default String getLanguageScriptUrl() {return "";} - StudyCheckAction getCheckAction(); + @NotNull + StudyTaskChecker getPyCharmTaskChecker(@NotNull PyCharmTask task, @NotNull Project project); @NotNull default DefaultActionGroup getTaskDescriptionActionGroup() { final DefaultActionGroup group = new DefaultActionGroup(); - group.add(getCheckAction()); - String[] ids = new String[]{StudyPreviousTaskAction.ACTION_ID, StudyNextTaskAction.ACTION_ID, StudyRefreshTaskFileAction.ACTION_ID, + String[] ids = new String[]{StudyCheckAction.ACTION_ID, StudyPreviousTaskAction.ACTION_ID, StudyNextTaskAction.ACTION_ID, StudyRefreshTaskFileAction.ACTION_ID, StudyShowHintAction.ACTION_ID}; for (String id : ids) { AnAction action = ActionManager.getInstance().getAction(id); diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java index f8d160ee8891..c206b1e68979 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java @@ -37,7 +37,7 @@ import javax.swing.*; public class StudyCheckAction extends StudyActionWithShortcut { public static final String SHORTCUT = "ctrl alt pressed ENTER"; - private static final String ACTION_ID = "Edu.Check"; + public static final String ACTION_ID = "Edu.Check"; private static final String TEXT = "Check Task"; public static final String FAILED_CHECK_LAUNCH = "Failed to launch checking"; @@ -109,16 +109,19 @@ public class StudyCheckAction extends StudyActionWithShortcut { listener.afterCheck(project, task); } checker.clearState(); + myCheckInProgress.set(false); } @Override public void onCancel() { checker.clearState(); + myCheckInProgress.set(false); } @Override public void run(@NotNull ProgressIndicator indicator) { indicator.setIndeterminate(true); + myCheckInProgress.set(true); myResult = isRemote ? checkOnRemote() : checker.check(); } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java deleted file mode 100644 index 6f1eacf282e0..000000000000 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.jetbrains.edu.learning.checker; - -import com.intellij.execution.process.CapturingProcessHandler; -import com.intellij.execution.process.ProcessOutput; -import com.intellij.ide.projectView.ProjectView; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.StudySettings; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; -import com.jetbrains.edu.learning.courseFormat.tasks.ChoiceTask; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.courseFormat.tasks.TheoryTask; -import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; -import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StepicUser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable { - - private static final Logger LOG = Logger.getInstance(StudyCheckTask.class); - private final Project myProject; - protected final StudyState myStudyState; - protected final Task myTask; - protected final VirtualFile myTaskDir; - protected final StudyTaskManager myTaskManger; - private final StudyStatus myStatusBeforeCheck; - private final Ref myCheckInProcess; - private final Process myTestProcess; - private final String myCommandLine; - private static final String FAILED_CHECK_LAUNCH = "Failed to launch checking"; - - public StudyCheckTask(Project project, StudyState studyState, Ref checkInProcess, Process testProcess, String commandLine) { - super(project, "Checking Task"); - myProject = project; - myStudyState = studyState; - myCheckInProcess = checkInProcess; - myTestProcess = testProcess; - myCommandLine = commandLine; - myTask = studyState.getTask(); - myTaskDir = studyState.getTaskDir(); - myTaskManger = StudyTaskManager.getInstance(myProject); - myStatusBeforeCheck = myTask.getStatus(); - } - - @Override - public void onSuccess() { - StudyUtils.updateToolWindows(myProject); - StudyCheckUtils.drawAllPlaceholders(myProject, myTask); - ProjectView.getInstance(myProject).refresh(); - clearState(); - } - - protected void clearState() { - EduUtils.deleteWindowDescriptions(myTask, myTaskDir); - myCheckInProcess.set(false); - } - - @Override - public void onCancel() { - myTask.setStatus(myStatusBeforeCheck); - clearState(); - } - - @Override - public void run(@NotNull ProgressIndicator indicator) { - final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course != null) { - if (course.isAdaptive()) { - checkForAdaptiveCourse(indicator); - } - else { - checkForEduCourse(indicator); - } - runAfterTaskCheckedActions(); - } - } - - private void checkForEduCourse(@NotNull ProgressIndicator indicator) { - final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator); - if (testsOutput != null) { - if (testsOutput.isSuccess()) { - onTaskSolved(testsOutput.getMessage()); - } - else { - onTaskFailed(testsOutput.getMessage()); - } - final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course != null && EduNames.STUDY.equals(course.getCourseMode())) { - StepicUser user = StudySettings.getInstance().getUser(); - if (user != null) { - EduStepicConnector.postSolution(myTask, testsOutput.isSuccess(), myProject); - } - } - } - } - - @Nullable - private StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull ProgressIndicator indicator) { - final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine); - final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator); - if (indicator.isCanceled()) { - ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject)); - } - final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - if (course != null) { - final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive()); - String stderr = output.getStderr(); - if (!stderr.isEmpty() && output.getStdout().isEmpty()) { - //log error output of tests - LOG.info("#educational " + stderr); - return new StudyTestsOutputParser.TestsOutput(false, stderr); - } - return testsOutput; - } - return null; - } - - private void checkForAdaptiveCourse(@NotNull ProgressIndicator indicator) { - final StepicUser user = StudySettings.getInstance().getUser(); - if (user == null) { - LOG.warn("User is null"); - ApplicationManager.getApplication().invokeLater(() -> - StudyUtils.showErrorPopupOnToolbar(myProject, - "Failed to launch checking: you're not authorized")); - return; - } - - if (myTask instanceof ChoiceTask) { - final Pair result = EduAdaptiveStepicConnector.checkChoiceTask(myProject, (ChoiceTask)myTask, user); - processStepicCheckOutput(result); - } - else if (myTask instanceof TheoryTask) { - myTask.setStatus(StudyStatus.Solved); - } - else { - final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator); - if (testOutput != null) { - // As tests in adaptive courses are created from - // samples and stored in task, to disable it we should ignore local testing results - if (StudySettings.getInstance().isEnableTestingFromSamples() && !testOutput.isSuccess()) { - onTaskFailed(testOutput.getMessage()); - } - else { - final Pair pair = EduAdaptiveStepicConnector.checkCodeTask(myProject, myTask, user); - processStepicCheckOutput(pair); - } - } - } - } - - private void processStepicCheckOutput(@Nullable Pair pair) { - if (pair != null && pair.getFirst() != null) { - if (pair.getFirst()) { - onTaskSolved("Congratulations! Remote tests passed."); - } - else { - final String checkMessage = pair.getSecond(); - onTaskFailed(checkMessage); - } - } - else { - ApplicationManager.getApplication().invokeLater(() -> { - String message = pair == null ? FAILED_CHECK_LAUNCH : pair.getSecond(); - StudyCheckUtils.showTestResultPopUp(message, - MessageType.WARNING - .getPopupBackground(), - myProject); - }); - } - } - - protected void onTaskFailed(@NotNull String message) { - myTask.setStatus(StudyStatus.Failed); - myTask.getChecker(myProject).onTaskFailed(message); - } - - protected void onTaskSolved(@NotNull String message) { - myTask.setStatus(StudyStatus.Solved); - myTask.getChecker(myProject).onTaskSolved(message); - } - - private void runAfterTaskCheckedActions() { - for (StudyCheckListener listener : StudyCheckListener.EP_NAME.getExtensions()) { - listener.afterCheck(myProject, myTask); - } - } -} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java index 714433bcb03b..5309c6cba55f 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java @@ -1,6 +1,8 @@ package com.jetbrains.edu.learning.checker; import com.intellij.execution.impl.ConsoleViewImpl; +import com.intellij.execution.process.CapturingProcessHandler; +import com.intellij.execution.process.ProcessOutput; import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -10,6 +12,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.TaskInfo; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.Balloon; @@ -223,4 +226,18 @@ public class StudyCheckUtils { } }); } + + public static StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull Process testProcess, + @NotNull String commandLine, + boolean isAdaptive) { + final CapturingProcessHandler handler = new CapturingProcessHandler(testProcess, null, commandLine); + final ProcessOutput output = handler.runProcessWithProgressIndicator(ProgressManager.getInstance().getProgressIndicator()); + final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, isAdaptive); + String stderr = output.getStderr(); + if (!stderr.isEmpty() && output.getStdout().isEmpty()) { + LOG.info("#educational " + stderr); + return new StudyTestsOutputParser.TestsOutput(false, stderr); + } + return testsOutput; + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTestRunner.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTestRunner.java index 6e292a64cac5..9fd0b70a232a 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTestRunner.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTestRunner.java @@ -1,6 +1,7 @@ package com.jetbrains.edu.learning.checker; import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.edu.learning.courseFormat.tasks.Task; @@ -16,4 +17,6 @@ public abstract class StudyTestRunner {protected final Task myTask; public abstract Process createCheckProcess(@NotNull final Project project, @NotNull final String executablePath) throws ExecutionException; + public abstract GeneralCommandLine getCommandLine(); + } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/PyCharmTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/PyCharmTask.java index 7ec30c7bf90f..bf32ade4ba8a 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/PyCharmTask.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/PyCharmTask.java @@ -1,5 +1,9 @@ package com.jetbrains.edu.learning.courseFormat.tasks; +import com.intellij.openapi.project.Project; +import com.jetbrains.edu.learning.EduPluginConfigurator; +import com.jetbrains.edu.learning.checker.StudyTaskChecker; +import com.jetbrains.edu.learning.courseFormat.Course; import org.jetbrains.annotations.NotNull; /** @@ -18,4 +22,11 @@ public class PyCharmTask extends Task { public String getTaskType() { return "pycharm"; } + + @Override + public StudyTaskChecker getChecker(@NotNull Project project) { + Course course = getLesson().getCourse(); + EduPluginConfigurator configurator = EduPluginConfigurator.INSTANCE.forLanguage(course.getLanguageById()); + return configurator != null ? configurator.getPyCharmTaskChecker(this, project) : super.getChecker(project); + } } \ No newline at end of file diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java index db60c2c6b3a9..9e43a500c9a5 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java @@ -34,7 +34,7 @@ public class TheoryTask extends Task { @Override public StudyCheckResult checkOnRemote(@NotNull StepicUser user) { - return new StudyCheckResult(StudyStatus.Solved, ""); + return check(); } }; } diff --git a/python/educational-python/Edu-Python/resources/META-INF/plugin.xml b/python/educational-python/Edu-Python/resources/META-INF/plugin.xml index 70ba9c6a50eb..cce8e104067e 100644 --- a/python/educational-python/Edu-Python/resources/META-INF/plugin.xml +++ b/python/educational-python/Edu-Python/resources/META-INF/plugin.xml @@ -45,7 +45,6 @@ - diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java index 1bf1ad6c2fc4..bd224e42f31c 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyEduPluginConfigurator.java @@ -19,9 +19,10 @@ import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiManager; import com.intellij.util.PathUtil; import com.jetbrains.edu.coursecreator.settings.CCSettings; -import com.jetbrains.edu.learning.actions.StudyCheckAction; +import com.jetbrains.edu.learning.checker.StudyTaskChecker; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TaskWithSubtasks; import com.jetbrains.python.PythonModuleTypeBase; @@ -131,8 +132,9 @@ public class PyEduPluginConfigurator implements EduPluginConfigurator { } @Override - public StudyCheckAction getCheckAction() { - return new PyStudyCheckAction(); + @NotNull + public StudyTaskChecker getPyCharmTaskChecker(@NotNull PyCharmTask task, @NotNull Project project) { + return new PyStudyTaskChecker(task, project); } @Override diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java deleted file mode 100644 index 1948c1ce49a8..000000000000 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.jetbrains.edu.learning; - -import com.intellij.execution.ExecutionException; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.IdeFocusManager; -import com.jetbrains.edu.learning.actions.StudyCheckAction; -import com.jetbrains.edu.learning.checker.StudyCheckTask; -import com.jetbrains.edu.learning.checker.StudyCheckUtils; -import com.jetbrains.edu.learning.checker.StudyTestRunner; -import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.courseFormat.tasks.Task; -import com.jetbrains.edu.learning.editor.StudyEditor; -import com.jetbrains.edu.learning.statistics.EduUsagesCollector; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - -public class PyStudyCheckAction extends StudyCheckAction { - private static final Logger LOG = Logger.getInstance(PyStudyCheckAction.class); - public static final String ACTION_ID = "PyCheckAction"; - - public void check(@NotNull Project project) { - EduUsagesCollector.taskChecked(); - ApplicationManager.getApplication().runWriteAction(() -> CommandProcessor.getInstance().runUndoTransparentAction(() -> { - final StudyEditor selectedEditor = StudyUtils.getSelectedStudyEditor(project); - if (selectedEditor == null) return; - final StudyState studyState = new StudyState(selectedEditor); - if (!studyState.isValid()) { - LOG.info("StudyCheckAction was invoked outside study editor"); - return; - } - if (StudyCheckUtils.hasBackgroundProcesses(project)) return; - - final Task task = studyState.getTask(); - final VirtualFile taskDir = studyState.getTaskDir(); - StudyCheckUtils.flushWindows(task, taskDir); - - - ApplicationManager.getApplication().invokeLater( - () -> IdeFocusManager.getInstance(project).requestFocus(studyState.getEditor().getComponent(), true)); - - final StudyTestRunner testRunner = new PyStudyTestRunner(task, taskDir); - Process testProcess = null; - String commandLine = ""; - try { - final VirtualFile executablePath = getTaskVirtualFile(studyState, task, taskDir); - if (executablePath != null) { - commandLine = executablePath.getPath(); - testProcess = testRunner.createCheckProcess(project, commandLine); - } - } - catch (ExecutionException e) { - LOG.error(e); - } - if (testProcess == null) { - return; - } - myCheckInProgress.set(true); - StudyCheckTask checkTask = getCheckTask(project, studyState, testRunner, testProcess, commandLine); - ProgressManager.getInstance().run(checkTask); - })); - } - - @NotNull - private StudyCheckTask getCheckTask(@NotNull final Project project, - final StudyState studyState, - final StudyTestRunner testRunner, - final Process testProcess, - final String commandLine) { - return new StudyCheckTask(project, studyState, myCheckInProgress, testProcess, commandLine) { - @Override - protected void onTaskFailed(@NotNull String message) { - super.onTaskFailed(message); - ApplicationManager.getApplication().invokeLater(() -> { - if (myTaskDir == null) return; - for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { - final String name = entry.getKey(); - final TaskFile taskFile = entry.getValue(); - if (taskFile.getActivePlaceholders().size() < 2) { - continue; - } - final Course course = myTaskManger.getCourse(); - if (course != null && EduNames.STUDY.equals(course.getCourseMode())) { - CommandProcessor.getInstance().runUndoTransparentAction( - () -> ApplicationManager.getApplication().runWriteAction( - () -> StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project))); - } - } - StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project); - }); - } - }; - } - - @Nullable - private static VirtualFile getTaskVirtualFile(@NotNull final StudyState studyState, - @NotNull final Task task, - @NotNull final VirtualFile taskDir) { - VirtualFile taskVirtualFile = studyState.getVirtualFile(); - for (Map.Entry entry : task.getTaskFiles().entrySet()) { - String name = entry.getKey(); - TaskFile taskFile = entry.getValue(); - VirtualFile virtualFile = taskDir.findFileByRelativePath(name); - if (virtualFile != null) { - if (!taskFile.getActivePlaceholders().isEmpty()) { - taskVirtualFile = virtualFile; - } - } - } - return taskVirtualFile; - } - - @NotNull - @Override - public String getActionId() { - return ACTION_ID; - } -} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInstructionPainter.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInstructionPainter.java index 61f09c95fb4b..5fb22db0fd8a 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInstructionPainter.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyInstructionPainter.java @@ -20,7 +20,7 @@ public class PyStudyInstructionPainter extends EditorEmptyTextPainter { appendAction(painter, "Navigate between tasks", getActionShortcutText(StudyPreviousTaskAction.ACTION_ID) + separator + getActionShortcutText(StudyNextTaskAction.ACTION_ID)); appendAction(painter, "Reset current task file", getActionShortcutText(StudyRefreshTaskFileAction.ACTION_ID)); - appendAction(painter, "Check task", getActionShortcutText(PyStudyCheckAction.ACTION_ID)); + appendAction(painter, "Check task", getActionShortcutText(StudyCheckAction.ACTION_ID)); appendAction(painter, "Get hint for the answer placeholder", getActionShortcutText(StudyShowHintAction.ACTION_ID)); } } \ No newline at end of file diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTaskChecker.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTaskChecker.java new file mode 100644 index 000000000000..9d9f219d949a --- /dev/null +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTaskChecker.java @@ -0,0 +1,114 @@ +package com.jetbrains.edu.learning; + +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.learning.actions.StudyCheckAction; +import com.jetbrains.edu.learning.checker.*; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.core.EduUtils; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseFormat.tasks.PyCharmTask; +import com.jetbrains.edu.learning.courseFormat.tasks.Task; +import com.jetbrains.edu.learning.stepic.EduStepicConnector; +import com.jetbrains.edu.learning.stepic.StepicUser; +import one.util.streamex.EntryStream; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public class PyStudyTaskChecker extends StudyTaskChecker { + private static final Logger LOG = Logger.getInstance(PyStudyTaskChecker.class); + + public PyStudyTaskChecker(PyCharmTask task, Project project) { + super(task, project); + } + + @Override + public StudyCheckResult check() { + VirtualFile taskDir = myTask.getTaskDir(myProject); + if (taskDir == null) { + LOG.info("taskDir is null for task " + myTask.getName()); + return new StudyCheckResult(StudyStatus.Unchecked, "Task is broken"); + } + ApplicationManager.getApplication() + .invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.flushWindows(myTask, taskDir))); + final StudyTestRunner testRunner = new PyStudyTestRunner(myTask, taskDir); + try { + final VirtualFile fileToCheck = getTaskVirtualFile(myTask, taskDir); + if (fileToCheck != null) { + Process testProcess = testRunner.createCheckProcess(myProject, fileToCheck.getPath()); + StudyTestsOutputParser.TestsOutput output = + StudyCheckUtils + .getTestOutput(testProcess, testRunner.getCommandLine().getCommandLineString(), myTask.getLesson().getCourse().isAdaptive()); + return new StudyCheckResult(output.isSuccess() ? StudyStatus.Solved : StudyStatus.Failed, output.getMessage()); + } + } + catch (ExecutionException e) { + LOG.error(e); + } + return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); + } + + @Override + public void clearState() { + ApplicationManager.getApplication().invokeLater(() -> { + StudyCheckUtils.drawAllPlaceholders(myProject, myTask); + VirtualFile taskDir = myTask.getTaskDir(myProject); + if (taskDir != null) { + EduUtils.deleteWindowDescriptions(myTask, taskDir); + } + }); + } + + @Override + public void onTaskFailed(@NotNull String message) { + super.onTaskFailed(message); + ApplicationManager.getApplication().invokeLater(() -> { + VirtualFile taskDir = myTask.getTaskDir(myProject); + if (taskDir == null) return; + for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { + final String name = entry.getKey(); + final TaskFile taskFile = entry.getValue(); + if (taskFile.getActivePlaceholders().size() < 2) { + continue; + } + final Course course = myTask.getLesson().getCourse(); + if (course != null && EduNames.STUDY.equals(course.getCourseMode())) { + CommandProcessor.getInstance().runUndoTransparentAction( + () -> ApplicationManager.getApplication().runWriteAction( + () -> StudyCheckUtils.runSmartTestProcess(taskDir, new PyStudyTestRunner(myTask, taskDir), name, taskFile, myProject))); + } + } + StudyCheckUtils.navigateToFailedPlaceholder(new StudyState(StudyUtils.getSelectedStudyEditor(myProject)), myTask, taskDir, myProject); + }); + } + + @Nullable + private static VirtualFile getTaskVirtualFile(@NotNull final Task task, + @NotNull final VirtualFile taskDir) { + + Map fileMap = + EntryStream.of(task.getTaskFiles()).invert().mapValues(name -> taskDir.findFileByRelativePath(name)).nonNullValues().toMap(); + Map.Entry entry = EntryStream.of(fileMap).findAny(e -> !e.getKey().getActivePlaceholders().isEmpty()) + .orElse(fileMap.entrySet().stream().findFirst().orElse(null)); + return entry == null ? null : entry.getValue(); + } + + @Override + public StudyCheckResult checkOnRemote(@NotNull StepicUser user) { + StudyCheckResult result = check(); + final Course course = StudyTaskManager.getInstance(myProject).getCourse(); + StudyStatus status = result.getStatus(); + if (course != null && EduNames.STUDY.equals(course.getCourseMode()) && status != StudyStatus.Unchecked) { + EduStepicConnector.postSolution(myTask, status == StudyStatus.Solved, myProject); + } + return result; + } +} diff --git a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java index 6720691d37f6..e8c506d64025 100644 --- a/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java +++ b/python/educational-python/Edu-Python/src/com/jetbrains/edu/learning/PyStudyTestRunner.java @@ -20,6 +20,7 @@ import java.util.Map; public class PyStudyTestRunner extends StudyTestRunner { private static final String PYTHONPATH = "PYTHONPATH"; + private GeneralCommandLine myCommandLine; PyStudyTestRunner(@NotNull final Task task, @NotNull final VirtualFile taskDir) { super(task, taskDir); @@ -35,9 +36,9 @@ public class PyStudyTestRunner extends StudyTestRunner { testsFileName += EduNames.SUBTASK_MARKER + index + "." + FileUtilRt.getExtension(configurator.getTestFileName()); } final File testRunner = new File(myTaskDir.getPath(), testsFileName); - final GeneralCommandLine commandLine = new GeneralCommandLine(); - commandLine.withWorkDirectory(myTaskDir.getPath()); - final Map env = commandLine.getEnvironment(); + myCommandLine = new GeneralCommandLine(); + myCommandLine.withWorkDirectory(myTaskDir.getPath()); + final Map env = myCommandLine.getEnvironment(); final VirtualFile courseDir = project.getBaseDir(); if (courseDir != null) { @@ -46,12 +47,17 @@ public class PyStudyTestRunner extends StudyTestRunner { if (sdk != null) { String pythonPath = sdk.getHomePath(); if (pythonPath != null) { - commandLine.setExePath(pythonPath); - commandLine.addParameter(testRunner.getPath()); - commandLine.addParameter(FileUtil.toSystemDependentName(executablePath)); - return commandLine.createProcess(); + myCommandLine.setExePath(pythonPath); + myCommandLine.addParameter(testRunner.getPath()); + myCommandLine.addParameter(FileUtil.toSystemDependentName(executablePath)); + return myCommandLine.createProcess(); } } return null; } + + @Override + public GeneralCommandLine getCommandLine() { + return myCommandLine; + } }