replace run tests action with run configuration
authorliana.bakradze <liana.bakradze@jetbrains.com>
Wed, 20 Apr 2016 14:51:31 +0000 (17:51 +0300)
committerliana.bakradze <liana.bakradze@jetbrains.com>
Wed, 20 Apr 2016 14:51:31 +0000 (17:51 +0300)
13 files changed:
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCStudyActionListener.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java
python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java [deleted file]
python/educational-python/course-creator-python/resources/META-INF/plugin.xml
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCRunTests.java [deleted file]
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/actions/PyCCRunTestsAction.java [deleted file]
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java [new file with mode: 0644]
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestConfiguration.java [new file with mode: 0644]
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationFactory.java [new file with mode: 0644]
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java [new file with mode: 0644]
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationType.java [new file with mode: 0644]
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingEditor.form [new file with mode: 0644]
python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingsEditor.java [new file with mode: 0644]

index 921f976971155fe1abec46020a990b5504ed0660..945026bd1ff9aa13cff76250db0d73cd1cbcd885 100644 (file)
@@ -2,20 +2,13 @@ package com.jetbrains.edu.coursecreator;
 
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.util.DocumentUtil;
 import com.jetbrains.edu.learning.StudyActionListener;
 import com.jetbrains.edu.learning.StudyUtils;
-import com.jetbrains.edu.learning.core.EduUtils;
-import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder;
 import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 
-import java.util.Map;
-
 public class CCStudyActionListener implements StudyActionListener {
   @Override
   public void beforeCheck(AnActionEvent event) {
@@ -38,28 +31,6 @@ public class CCStudyActionListener implements StudyActionListener {
     if (taskDir == null) {
       return;
     }
-    Map<String, TaskFile> files = task.getTaskFiles();
-    for (Map.Entry<String, TaskFile> entry : files.entrySet()) {
-      String name = entry.getKey();
-      VirtualFile child = taskDir.findChild(name);
-      if (child == null) {
-        continue;
-      }
-      Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name);
-      Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
-      if (document == null || patternDocument == null) {
-        return;
-      }
-      DocumentUtil.writeInRunUndoTransparentAction(() -> {
-        patternDocument.replaceString(0, patternDocument.getTextLength(), document.getCharsSequence());
-        FileDocumentManager.getInstance().saveDocument(patternDocument);
-      });
-      TaskFile target = new TaskFile();
-      TaskFile.copy(taskFile, target);
-      for (AnswerPlaceholder placeholder : target.getAnswerPlaceholders()) {
-        placeholder.setUseLength(false);
-      }
-      EduUtils.createStudentDocument(project, target, child, patternDocument);
-    }
+    CCUtils.createResources(project, task, taskDir);
   }
 }
index 78ad05d7aaa086e42f7d285ce8a0aea706126569..f6dd7417a32d7b0b86547fd3e967b64709d89cff 100644 (file)
@@ -6,6 +6,8 @@ import com.intellij.ide.projectView.actions.MarkRootActionBase;
 import com.intellij.lang.Language;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.module.Module;
 import com.intellij.openapi.project.DumbModePermission;
 import com.intellij.openapi.project.DumbService;
@@ -18,20 +20,18 @@ import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileEvent;
 import com.intellij.psi.PsiDirectory;
+import com.intellij.util.DocumentUtil;
 import com.intellij.util.Function;
 import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.core.EduUtils;
-import com.jetbrains.edu.learning.courseFormat.Course;
-import com.jetbrains.edu.learning.courseFormat.StudyItem;
+import com.jetbrains.edu.learning.courseFormat.*;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
+import java.util.*;
 
 public class CCUtils {
   private static final Logger LOG = Logger.getInstance(CCUtils.class);
@@ -214,4 +214,31 @@ public class CCUtils {
       LOG.info("Failed to copy created task file to resources " + createdFile.getPath());
     }
   }
+
+
+  public static void createResources(Project project, Task task, VirtualFile taskDir) {
+    Map<String, TaskFile> files = task.getTaskFiles();
+    for (Map.Entry<String, TaskFile> entry : files.entrySet()) {
+      String name = entry.getKey();
+      VirtualFile child = taskDir.findChild(name);
+      if (child == null) {
+        continue;
+      }
+      Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name);
+      Document document = FileDocumentManager.getInstance().getDocument(child);
+      if (document == null || patternDocument == null) {
+        return;
+      }
+      DocumentUtil.writeInRunUndoTransparentAction(() -> {
+        patternDocument.replaceString(0, patternDocument.getTextLength(), document.getCharsSequence());
+        FileDocumentManager.getInstance().saveDocument(patternDocument);
+      });
+      TaskFile target = new TaskFile();
+      TaskFile.copy(entry.getValue(), target);
+      for (AnswerPlaceholder placeholder : target.getAnswerPlaceholders()) {
+        placeholder.setUseLength(false);
+      }
+      EduUtils.createStudentDocument(project, target, child, patternDocument);
+    }
+  }
 }
diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCRunTestsAction.java
deleted file mode 100644 (file)
index f698273..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.edu.coursecreator.actions;
-
-import com.intellij.execution.Location;
-import com.intellij.execution.actions.ConfigurationContext;
-import com.intellij.icons.AllIcons;
-import com.intellij.ide.fileTemplates.FileTemplate;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.Presentation;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.fileEditor.FileDocumentManager;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiFile;
-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.Lesson;
-import com.jetbrains.edu.learning.courseFormat.Task;
-import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.coursecreator.CCLanguageManager;
-import com.jetbrains.edu.coursecreator.CCProjectService;
-import com.jetbrains.edu.coursecreator.CCUtils;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.IOException;
-import java.util.Map;
-
-public abstract class CCRunTestsAction extends AnAction {
-  private static final Logger LOG = Logger.getInstance(CCRunTestsAction.class.getName());
-
-  public CCRunTestsAction() {
-    getTemplatePresentation().setIcon(AllIcons.Actions.Lightning);
-  }
-
-  @Override
-  public void update(@NotNull AnActionEvent e) {
-    final Presentation presentation = e.getPresentation();
-    if (!CCProjectService.setCCActionAvailable(e)) {
-      presentation.setEnabledAndVisible(false);
-      return;
-    }
-
-    presentation.setEnabledAndVisible(false);
-
-    final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
-    Location location = context.getLocation();
-    if (location == null) {
-      return;
-    }
-    PsiElement psiElement = location.getPsiElement();
-    PsiFile psiFile = psiElement.getContainingFile();
-    Project project = e.getProject();
-    if (project == null || psiFile == null) {
-      return;
-    }
-    TaskFile taskFile = CCProjectService.getInstance(project).getTaskFile(psiFile.getVirtualFile());
-    if (taskFile == null) {
-      return;
-    }
-    presentation.setEnabledAndVisible(true);
-    presentation.setText("Run tests from '" + FileUtil.getNameWithoutExtension(psiFile.getName()) + "'");
-  }
-
-  public void actionPerformed(@NotNull AnActionEvent e) {
-    final ConfigurationContext context = ConfigurationContext.getFromContext(e.getDataContext());
-    run(context);
-  }
-
-  private void run(final @NotNull ConfigurationContext context) {
-    ApplicationManager.getApplication().runWriteAction(new Runnable() {
-      @Override
-      public void run() {
-        final Project project = context.getProject();
-        PsiElement location = context.getPsiLocation();
-        final Course course = CCProjectService.getInstance(project).getCourse();
-        if (course == null || location == null) {
-          return;
-        }
-        PsiFile psiFile = location.getContainingFile();
-        final VirtualFile virtualFile = psiFile.getVirtualFile();
-        final VirtualFile taskDir = virtualFile.getParent();
-        if (taskDir == null) {
-          return;
-        }
-        VirtualFile lessonDir = taskDir.getParent();
-        Lesson lesson = course.getLesson(lessonDir.getName());
-        if (lesson == null) {
-          return;
-        }
-        final Task task = lesson.getTask(taskDir.getName());
-        if (task == null) {
-          return;
-        }
-        clearTestEnvironment(taskDir, project);
-        CCLanguageManager manager = CCUtils.getStudyLanguageManager(course);
-        if (manager == null) {
-          return;
-        }
-        for (final Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
-          final String name = entry.getKey();
-          createTaskFileForTest(taskDir, name, entry.getValue(), project);
-        }
-        FileTemplate testsTemplate = manager.getTestsTemplate(project);
-        if (testsTemplate == null) {
-          return;
-        }
-        VirtualFile testFile = taskDir.findChild(testsTemplate.getName() + "." + testsTemplate.getExtension());
-        if (testFile == null) {
-          return;
-        }
-        executeTests(project, virtualFile, taskDir, testFile);
-      }
-    });
-  }
-
-  private static void createTaskFileForTest(@NotNull final VirtualFile taskDir, final String fileName, @NotNull final TaskFile taskFile,
-                                            @NotNull final Project project) {
-    final VirtualFile answerFile = taskDir.findChild(fileName);
-    if (answerFile == null) {
-      LOG.debug("could not find answer file " + fileName);
-      return;
-    }
-    ApplicationManager.getApplication().runWriteAction(new Runnable() {
-      @Override
-      public void run() {
-        final FileDocumentManager documentManager = FileDocumentManager.getInstance();
-        documentManager.saveAllDocuments();
-      }
-    });
-    EduUtils.flushWindows(taskFile, answerFile, false);
-    createResourceFiles(answerFile, project);
-  }
-
-  public static void clearTestEnvironment(@NotNull final VirtualFile taskDir, @NotNull final Project project) {
-    try {
-      VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
-      if (ideaDir == null) {
-        LOG.debug("idea directory doesn't exist");
-        return;
-      }
-      VirtualFile courseResourceDir = ideaDir.findChild(EduNames.COURSE);
-      if (courseResourceDir != null) {
-        courseResourceDir.delete(project);
-      }
-      VirtualFile[] taskDirChildren = taskDir.getChildren();
-      for (VirtualFile file : taskDirChildren) {
-        if (file.getName().contains(EduNames.WINDOWS_POSTFIX)) {
-          file.delete(project);
-        }
-      }
-    }
-    catch (IOException e) {
-      LOG.error(e);
-    }
-  }
-
-  protected abstract void executeTests(@NotNull final Project project,
-                                       @NotNull final VirtualFile virtualFile,
-                                       @NotNull final VirtualFile taskDir,
-                                       @NotNull final VirtualFile testFile);
-
-  //some tests could compare task files after user modifications with initial task files
-  private static void createResourceFiles(@NotNull final VirtualFile file, @NotNull final Project project) {
-    VirtualFile taskDir = file.getParent();
-    int index = EduUtils.getIndex(taskDir.getName(), EduNames.TASK);
-    VirtualFile lessonDir = taskDir.getParent();
-    int lessonIndex = EduUtils.getIndex(lessonDir.getName(), EduNames.LESSON);
-    Course course = CCProjectService.getInstance(project).getCourse();
-    if (course == null) {
-      return;
-    }
-    VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
-    assert ideaDir != null;
-    try {
-      VirtualFile courseResourceDir = findOrCreateDir(project, ideaDir, EduNames.COURSE);
-      VirtualFile lessonResourceDir = findOrCreateDir(project, courseResourceDir, lessonDir.getName());
-      VirtualFile taskResourceDir = findOrCreateDir(project, lessonResourceDir, taskDir.getName());
-      if (EduUtils.indexIsValid(lessonIndex, course.getLessons())) {
-        Lesson lesson = course.getLessons().get(lessonIndex);
-        if (EduUtils.indexIsValid(index, lesson.getTaskList())) {
-          Task task = lesson.getTaskList().get(index);
-          for (Map.Entry<String, TaskFile> entry : task.getTaskFiles().entrySet()) {
-            TaskFile taskFile = new TaskFile();
-            TaskFile.copy(entry.getValue(), taskFile);
-            EduUtils.createStudentFileFromAnswer(project, taskResourceDir, taskDir, entry.getKey(), taskFile);
-          }
-        }
-      }
-    }
-    catch (IOException e) {
-      LOG.error(e);
-    }
-  }
-
-  private static VirtualFile findOrCreateDir(@NotNull final Project project, @NotNull final VirtualFile dir, String name)
-    throws IOException {
-    VirtualFile targetDir = dir.findChild(name);
-    if (targetDir == null) {
-      targetDir = dir.createChildDirectory(project, name);
-    }
-    return targetDir;
-  }
-}
index e87961c59b402ff7221beb54afb0a6649a674157..2399560d07b7326287431507a2c445535756419d 100644 (file)
 
   <extensions defaultExtensionNs="com.intellij">
     <directoryProjectGenerator implementation="com.jetbrains.edu.coursecreator.PyCCProjectGenerator"/>
+    <configurationType implementation="com.jetbrains.edu.coursecreator.run.PyCCRunTestsConfigurationType"/>
+    <runConfigurationProducer implementation="com.jetbrains.edu.coursecreator.run.PyCCRunTestsConfigurationProducer"/>
   </extensions>
 
   <extensions defaultExtensionNs="Edu">
     <CCLanguageManager implementationClass="com.jetbrains.edu.coursecreator.PyCCLanguageManager" language="Python"/>
-    <executeFile implementation="com.jetbrains.edu.coursecreator.PyCCRunTests"/>
   </extensions>
 
   <extensions defaultExtensionNs="Pythonid">
     <pyReferenceResolveProvider implementation="com.jetbrains.edu.coursecreator.PyCCReferenceResolveProvider"/>
   </extensions>
 
-  <application-components>
-    <!-- Add your application components here -->
-  </application-components>
-
-  <project-components>
-    <!-- Add your project components here -->
-  </project-components>
-
-  <actions>
-    <action id="CCRunTests" class="com.jetbrains.edu.coursecreator.actions.PyCCRunTestsAction" text="Run Tests" description="Run tests"/>
-  </actions>
-
 </idea-plugin>
\ No newline at end of file
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCRunTests.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCRunTests.java
deleted file mode 100644 (file)
index 5af7f8b..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.jetbrains.edu.coursecreator;
-
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.project.Project;
-import com.jetbrains.edu.coursecreator.actions.PyCCRunTestsAction;
-import com.jetbrains.python.edu.PyExecuteFileExtensionPoint;
-import org.jetbrains.annotations.NotNull;
-
-public class PyCCRunTests implements PyExecuteFileExtensionPoint {
-
-  @NotNull
-  public AnAction getRunAction() {
-    return new PyCCRunTestsAction();
-  }
-
-  @Override
-  public boolean accept(Project project) {
-    return CCProjectService.getInstance(project).getCourse() != null;
-  }
-}
\ No newline at end of file
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/actions/PyCCRunTestsAction.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/actions/PyCCRunTestsAction.java
deleted file mode 100644 (file)
index ebda0f8..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2000-2014 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.jetbrains.edu.coursecreator.actions;
-
-import com.intellij.execution.Executor;
-import com.intellij.execution.ProgramRunnerUtil;
-import com.intellij.execution.RunManager;
-import com.intellij.execution.RunnerAndConfigurationSettings;
-import com.intellij.execution.configurations.ConfigurationFactory;
-import com.intellij.execution.executors.DefaultRunExecutor;
-import com.intellij.ide.projectView.ProjectView;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.util.PathUtil;
-import com.jetbrains.python.run.PythonConfigurationType;
-import com.jetbrains.python.run.PythonRunConfiguration;
-import org.jetbrains.annotations.NotNull;
-
-public class PyCCRunTestsAction extends CCRunTestsAction {
-
-  @Override
-  protected void executeTests(@NotNull final Project project,
-                                   @NotNull final VirtualFile virtualFile,
-                                   @NotNull final VirtualFile taskDir,
-                                   @NotNull final VirtualFile testFile) {
-    final ConfigurationFactory factory = PythonConfigurationType.getInstance().getConfigurationFactories()[0];
-    final RunnerAndConfigurationSettings settings =
-      RunManager.getInstance(project).createRunConfiguration("test", factory);
-
-    final PythonRunConfiguration configuration = (PythonRunConfiguration)settings.getConfiguration();
-    configuration.setScriptName(testFile.getPath());
-    configuration.setWorkingDirectory(taskDir.getPath());
-    String taskFileName = virtualFile.getName();
-    VirtualFile userFile = taskDir.findChild(taskFileName);
-    if (userFile == null) {
-      return;
-    }
-    VirtualFile ideaDir = project.getBaseDir().findChild(".idea");
-    if (ideaDir == null) {
-      return;
-    }
-    VirtualFileManager.getInstance().refreshWithoutFileWatcher(true);
-    ProjectView.getInstance(project).refresh();
-    configuration.setScriptParameters(PathUtil.toSystemDependentName(project.getBasePath()) + " " + PathUtil.toSystemDependentName(userFile.getPath()));
-    Executor executor = DefaultRunExecutor.getRunExecutorInstance();
-    ProgramRunnerUtil.executeConfiguration(project, settings, executor);
-  }
-}
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCCommandLineState.java
new file mode 100644 (file)
index 0000000..0bde809
--- /dev/null
@@ -0,0 +1,89 @@
+package com.jetbrains.edu.coursecreator.run;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.ExecutionResult;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.configurations.ParamsGroup;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.coursecreator.CCUtils;
+import com.jetbrains.edu.learning.StudyTaskManager;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.checker.StudyCheckUtils;
+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.Task;
+import com.jetbrains.python.run.CommandLinePatcher;
+import com.jetbrains.python.run.PythonCommandLineState;
+import org.jetbrains.annotations.NotNull;
+
+public class PyCCCommandLineState extends PythonCommandLineState {
+  private final PyCCRunTestConfiguration myRunConfiguration;
+  private final VirtualFile myTaskDir;
+  private final Task myTask;
+
+  public PyCCCommandLineState(PyCCRunTestConfiguration runConfiguration,
+                              ExecutionEnvironment env) {
+    super(runConfiguration, env);
+    myRunConfiguration = runConfiguration;
+
+    VirtualFile testsFile = LocalFileSystem.getInstance().findFileByPath(myRunConfiguration.getPathToTest());
+    assert testsFile != null;
+    myTaskDir = StudyUtils.getTaskDir(testsFile);
+    assert myTaskDir != null;
+    myTask = StudyUtils.getTask(myRunConfiguration.getProject(), myTaskDir);
+    assert myTask != null;
+  }
+
+  @Override
+  protected void buildCommandLineParameters(GeneralCommandLine commandLine) {
+    ParamsGroup group = commandLine.getParametersList().getParamsGroup(GROUP_SCRIPT);
+    assert group != null;
+
+    Project project = myRunConfiguration.getProject();
+    Course course = StudyTaskManager.getInstance(project).getCourse();
+    assert course != null;
+
+    group.addParameter(myRunConfiguration.getPathToTest());
+    group.addParameter(course.getCourseDirectory());
+
+    group.addParameter(getFirstTaskFilePath());
+  }
+
+  @NotNull
+  private String getFirstTaskFilePath() {
+    String firstTaskFileName = StudyUtils.getFirst(myTask.getTaskFiles().keySet());
+    return myTaskDir.findChild(EduNames.SRC) != null ?
+           FileUtil.join(myTaskDir.getPath(), EduNames.SRC, firstTaskFileName) :
+           FileUtil.join(myTaskDir.getPath(), firstTaskFileName);
+  }
+
+  @Override
+  public ExecutionResult execute(Executor executor, CommandLinePatcher... patchers) throws ExecutionException {
+    CCUtils.createResources(myRunConfiguration.getProject(), myTask, myTaskDir);
+    ApplicationManager.getApplication().runWriteAction(() -> StudyCheckUtils.flushWindows(myTask, myTaskDir));
+
+    return super.execute(executor, patchers);
+  }
+
+  @Override
+  protected ProcessHandler doCreateProcess(GeneralCommandLine commandLine) throws ExecutionException {
+    ProcessHandler handler = super.doCreateProcess(commandLine);
+    handler.addProcessListener(new ProcessAdapter() {
+      @Override
+      public void processTerminated(ProcessEvent event) {
+        ApplicationManager.getApplication().invokeLater(() -> EduUtils.deleteWindowDescriptions(myTask, myTaskDir));
+      }
+    });
+    return handler;
+  }
+}
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestConfiguration.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestConfiguration.java
new file mode 100644 (file)
index 0000000..aca074e
--- /dev/null
@@ -0,0 +1,80 @@
+package com.jetbrains.edu.coursecreator.run;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.configurations.RuntimeConfigurationException;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.InvalidDataException;
+import com.intellij.openapi.util.JDOMExternalizerUtil;
+import com.intellij.openapi.util.WriteExternalException;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.python.run.AbstractPythonRunConfiguration;
+import org.jdom.Element;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class PyCCRunTestConfiguration extends AbstractPythonRunConfiguration<PyCCRunTestConfiguration> {
+
+  public static final String PATH_ATTR = "studyTest";
+  private Project myProject;
+  private String myPathToTest;
+
+  public PyCCRunTestConfiguration(Project project, ConfigurationFactory factory) {
+    super(project, factory);
+    myProject = project;
+  }
+
+  @Nullable
+  @Override
+  public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) throws ExecutionException {
+    return new PyCCCommandLineState(this, environment);
+  }
+
+  @Override
+  protected SettingsEditor<PyCCRunTestConfiguration> createConfigurationEditor() {
+    return new PyCCSettingsEditor(myProject);
+  }
+
+  public String getPathToTest() {
+    return myPathToTest;
+  }
+
+  public void setPathToTest(String pathToTest) {
+    myPathToTest = pathToTest;
+  }
+
+  @Override
+  public void readExternal(Element element) throws InvalidDataException {
+    super.readExternal(element);
+    myPathToTest = JDOMExternalizerUtil.readField(element, PATH_ATTR);
+  }
+
+  @Override
+  public void writeExternal(Element element) throws WriteExternalException {
+    super.writeExternal(element);
+    JDOMExternalizerUtil.writeField(element, PATH_ATTR, myPathToTest);
+  }
+
+  @Override
+  public void checkConfiguration() throws RuntimeConfigurationException {
+    super.checkConfiguration();
+    String message = "Select valid path to the file with tests";
+    VirtualFile testsFile = LocalFileSystem.getInstance().findFileByPath(myPathToTest);
+    if (testsFile == null) {
+      throw new RuntimeConfigurationException(message);
+    }
+    VirtualFile taskDir = StudyUtils.getTaskDir(testsFile);
+    if (taskDir == null) {
+      throw new RuntimeConfigurationException(message);
+    }
+    if (StudyUtils.getTask(myProject, taskDir) == null) {
+      throw new RuntimeConfigurationException(message);
+    }
+  }
+}
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationFactory.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationFactory.java
new file mode 100644 (file)
index 0000000..1d2de89
--- /dev/null
@@ -0,0 +1,19 @@
+package com.jetbrains.edu.coursecreator.run;
+
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.ConfigurationType;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+
+public class PyCCRunTestsConfigurationFactory extends ConfigurationFactory {
+  protected PyCCRunTestsConfigurationFactory(@NotNull ConfigurationType type) {
+    super(type);
+  }
+
+  @NotNull
+  @Override
+  public RunConfiguration createTemplateConfiguration(@NotNull Project project) {
+    return new PyCCRunTestConfiguration(project, this);
+  }
+}
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationProducer.java
new file mode 100644 (file)
index 0000000..53ca0c9
--- /dev/null
@@ -0,0 +1,94 @@
+package com.jetbrains.edu.coursecreator.run;
+
+import com.intellij.execution.Location;
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.actions.RunConfigurationProducer;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.jetbrains.edu.coursecreator.CCUtils;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.core.EduNames;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class PyCCRunTestsConfigurationProducer extends RunConfigurationProducer<PyCCRunTestConfiguration> {
+  protected PyCCRunTestsConfigurationProducer() {
+    super(PyCCRunTestsConfigurationType.getInstance());
+  }
+
+  @Override
+  protected boolean setupConfigurationFromContext(PyCCRunTestConfiguration configuration,
+                                                  ConfigurationContext context,
+                                                  Ref<PsiElement> sourceElement) {
+    Project project = context.getProject();
+    if (!CCUtils.isCourseCreator(project)) {
+      return false;
+    }
+
+    String testsPath = getTestPath(context);
+    if (testsPath == null) {
+      return false;
+    }
+    VirtualFile testsFile = LocalFileSystem.getInstance().findFileByPath(testsPath);
+    if (testsFile == null) {
+      return false;
+    }
+
+    String generatedName = generateName(testsFile, project);
+    if (generatedName == null) {
+      return false;
+    }
+
+    configuration.setPathToTest(testsPath);
+    configuration.setName(generatedName);
+    return true;
+  }
+
+  @Nullable
+  private static String generateName(@NotNull VirtualFile testsFile, @NotNull Project project) {
+    VirtualFile taskDir = StudyUtils.getTaskDir(testsFile);
+    if (taskDir == null) {
+      return null;
+    }
+    Task task = StudyUtils.getTask(project, taskDir);
+    if (task == null) {
+      return null;
+    }
+    return task.getLesson().getName() + "/" + task.getName();
+  }
+
+  @Nullable
+  private static String getTestPath(@NotNull ConfigurationContext context) {
+    Location location = context.getLocation();
+    if (location == null) {
+      return null;
+    }
+    VirtualFile file = location.getVirtualFile();
+    if (file == null) {
+      return null;
+    }
+    VirtualFile taskDir = StudyUtils.getTaskDir(file);
+    if (taskDir == null) {
+      return null;
+    }
+
+    String testsPath = taskDir.findChild(EduNames.SRC) != null ?
+               FileUtil.join(taskDir.getPath(), EduNames.SRC, EduNames.TESTS_FILE) :
+               FileUtil.join(taskDir.getPath(), EduNames.TESTS_FILE);
+    return file.getPath().equals(testsPath) ? testsPath : null;
+  }
+
+  @Override
+  public boolean isConfigurationFromContext(PyCCRunTestConfiguration configuration, ConfigurationContext context) {
+    String path = getTestPath(context);
+    if (path == null) {
+      return false;
+    }
+    return path.equals(configuration.getPathToTest());
+  }
+}
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationType.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCRunTestsConfigurationType.java
new file mode 100644 (file)
index 0000000..ac05cf1
--- /dev/null
@@ -0,0 +1,41 @@
+package com.jetbrains.edu.coursecreator.run;
+
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.ConfigurationType;
+import com.intellij.execution.configurations.ConfigurationTypeUtil;
+import com.intellij.icons.AllIcons;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+public class PyCCRunTestsConfigurationType implements ConfigurationType {
+  @Override
+  public String getDisplayName() {
+    return "Run Study Tests";
+  }
+
+  @Override
+  public String getConfigurationTypeDescription() {
+    return "Study Test Runner";
+  }
+
+  @Override
+  public Icon getIcon() {
+    return AllIcons.Actions.Lightning;
+  }
+
+  @NotNull
+  @Override
+  public String getId() {
+    return "ccruntests";
+  }
+
+  @Override
+  public ConfigurationFactory[] getConfigurationFactories() {
+    return new ConfigurationFactory[]{new PyCCRunTestsConfigurationFactory(this)};
+  }
+
+  public static PyCCRunTestsConfigurationType getInstance() {
+    return ConfigurationTypeUtil.findConfigurationType(PyCCRunTestsConfigurationType.class);
+  }
+}
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingEditor.form b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingEditor.form
new file mode 100644 (file)
index 0000000..c84b034
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.edu.coursecreator.run.PyCCSettingsEditor">
+  <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+    <margin top="0" left="0" bottom="0" right="0"/>
+    <constraints>
+      <xy x="20" y="20" width="500" height="400"/>
+    </constraints>
+    <properties/>
+    <border type="none"/>
+    <children>
+      <component id="ae76e" class="javax.swing.JTextField" binding="myPathToTestFileField">
+        <constraints>
+          <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+            <preferred-size width="150" height="-1"/>
+          </grid>
+        </constraints>
+        <properties/>
+      </component>
+      <vspacer id="d7ebc">
+        <constraints>
+          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+        </constraints>
+      </vspacer>
+      <component id="704f6" class="com.intellij.ui.components.JBLabel">
+        <constraints>
+          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text value="Path to Test File"/>
+        </properties>
+      </component>
+    </children>
+  </grid>
+</form>
diff --git a/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingsEditor.java b/python/educational-python/course-creator-python/src/com/jetbrains/edu/coursecreator/run/PyCCSettingsEditor.java
new file mode 100644 (file)
index 0000000..806f6a0
--- /dev/null
@@ -0,0 +1,69 @@
+package com.jetbrains.edu.coursecreator.run;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.python.run.AbstractPyCommonOptionsForm;
+import com.jetbrains.python.run.AbstractPythonRunConfiguration;
+import com.jetbrains.python.run.PyCommonOptionsFormData;
+import com.jetbrains.python.run.PyCommonOptionsFormFactory;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.List;
+
+public class PyCCSettingsEditor extends SettingsEditor<PyCCRunTestConfiguration> {
+  private AbstractPyCommonOptionsForm myForm;
+  private Project myProject;
+  private JTextField myPathToTestFileField;
+  private JPanel myPanel;
+
+  public PyCCSettingsEditor(Project project) {
+    myProject = project;
+  }
+
+  @Override
+  protected void resetEditorFrom(PyCCRunTestConfiguration s) {
+    AbstractPythonRunConfiguration.copyParams(s, myForm);
+    myPathToTestFileField.setText(s.getPathToTest());
+  }
+
+  @Override
+  protected void applyEditorTo(PyCCRunTestConfiguration s) throws ConfigurationException {
+    AbstractPythonRunConfiguration.copyParams(myForm, s);
+    s.setPathToTest(myPathToTestFileField.getText());
+  }
+
+  @NotNull
+  @Override
+  protected JComponent createEditor() {
+    final JPanel mainPanel = new JPanel(new BorderLayout());
+    mainPanel.add(myPanel, BorderLayout.NORTH);
+    myForm = createEnvPanel();
+    mainPanel.add(myForm.getMainPanel(), BorderLayout.SOUTH);
+    return mainPanel;
+  }
+
+
+  @NotNull
+  private AbstractPyCommonOptionsForm createEnvPanel() {
+    return PyCommonOptionsFormFactory.getInstance().createForm(new PyCommonOptionsFormData() {
+      @Override
+      public Project getProject() {
+        return myProject;
+      }
+
+      @Override
+      public List<Module> getValidModules() {
+        return AbstractPythonRunConfiguration.getValidModules(myProject);
+      }
+
+      @Override
+      public boolean showConfigureInterpretersLink() {
+        return false;
+      }
+    });
+  }
+}