\Merge branch 'master' of ../../IDEA3\r\r# Please enter a commit message to explain...
authorDmitry Trofimov <dmitry.trofimov@jetbrains.com>
Tue, 12 Aug 2014 16:39:01 +0000 (18:39 +0200)
committerDmitry Trofimov <dmitry.trofimov@jetbrains.com>
Tue, 12 Aug 2014 16:39:01 +0000 (18:39 +0200)
26 files changed:
python/testSrc/com/jetbrains/env/PyEnvSufficiencyTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/PyEnvTestCase.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/PyExecutionFixtureTestTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/PyTestTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/IPythonConsoleTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/PyPackageRequirementsInspectionTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/PyPackagingTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/PythonConsoleTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/PythonGeneratorTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/PythonSkeletonsTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/console/PyConsoleTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/dotNet/PyIronPythonTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/dotNet/SkeletonTestTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/dotNet/package-info.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/testing/PythonDocTestingTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/ut/PyDocTestTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/ut/PyNoseTestTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/ut/PyTestTestTask.java [new file with mode: 0644]
python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java [new file with mode: 0644]

diff --git a/python/testSrc/com/jetbrains/env/PyEnvSufficiencyTest.java b/python/testSrc/com/jetbrains/env/PyEnvSufficiencyTest.java
new file mode 100644 (file)
index 0000000..61b64df
--- /dev/null
@@ -0,0 +1,51 @@
+package com.jetbrains.env.community;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.testFramework.UsefulTestCase;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author traff
+ */
+public class PyEnvSufficiencyTest extends PyEnvTestCase {
+  private static final List<String> BASE_TAGS =
+    ImmutableList.<String>builder().add("python3", "django", "jython", "ipython", "ipython011", "ipython012", "nose", "pytest").build();
+
+  public void testSufficiency() {
+    if (UsefulTestCase.IS_UNDER_TEAMCITY && IS_ENV_CONFIGURATION) {
+      Set<String> tags = Sets.newHashSet();
+      List<String> roots = getPythonRoots();
+      if (roots.size() == 0) {
+        return;         // not on env agent
+      }
+      for (String root : roots) {
+        tags.addAll(loadEnvTags(root));
+      }
+
+      List<String> missing = Lists.newArrayList();
+      for (String tag : necessaryTags()) {
+        if (!tags.contains(tag)) {
+          missing.add(tag);
+        }
+      }
+
+
+      assertEmpty("Agent is missing environments: " + StringUtil.join(missing, ", "), missing);
+    }
+  }
+
+  private static List<String> necessaryTags() {
+    if (SystemInfo.isWindows) {
+      return ImmutableList.<String>builder().addAll(BASE_TAGS).add("iron").build();
+    }
+    else {
+      return ImmutableList.<String>builder().addAll(BASE_TAGS).add("packaging").build();
+    }
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java b/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java
new file mode 100644 (file)
index 0000000..aab5f96
--- /dev/null
@@ -0,0 +1,112 @@
+package com.jetbrains.env.community;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.jetbrains.python.sdk.PythonSdkType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author traff
+ */
+public class PyEnvTaskRunner {
+  private final List<String> myRoots;
+
+  public PyEnvTaskRunner(List<String> roots) {
+    myRoots = roots;
+  }
+
+  public void runTask(PyTestTask testTask, String testName) {
+    boolean wasExecuted = false;
+
+    List<String> passedRoots = Lists.newArrayList();
+
+    for (String root : myRoots) {
+
+      if (!isSuitableForTask(PyEnvTestCase.loadEnvTags(root), testTask) || !shouldRun(root, testTask)) {
+        continue;
+      }
+
+      try {
+        testTask.setUp(testName);
+        wasExecuted = true;
+        if (isJython(root)) {
+          testTask.useLongTimeout();
+        }
+        else {
+          testTask.useNormalTimeout();
+        }
+        final String executable = getExecutable(root, testTask);
+        if (executable == null) {
+          throw new RuntimeException("Cannot find Python interpreter in " + root);
+        }
+        testTask.runTestOn(executable);
+        passedRoots.add(root);
+      }
+      catch (Throwable e) {
+        throw new RuntimeException(
+          PyEnvTestCase.joinStrings(passedRoots, "Tests passed environments: ") + "Test failed on " + getEnvType() + " environment " + root,
+          e);
+      }
+      finally {
+        try {
+          testTask.tearDown();
+        }
+        catch (Exception e) {
+          throw new RuntimeException("Couldn't tear down task", e);
+        }
+      }
+    }
+
+    if (!wasExecuted) {
+      throw new RuntimeException("test" +
+                                 testName +
+                                 " was not executed.\n" +
+                                 PyEnvTestCase.joinStrings(myRoots, "All roots: ") +
+                                 "\n" +
+                                 PyEnvTestCase.joinStrings(testTask.getTags(), "Required tags in tags.txt in root: "));
+    }
+  }
+
+  protected boolean shouldRun(String root, PyTestTask task) {
+    return true;
+  }
+
+  protected String getExecutable(String root, PyTestTask testTask) {
+    return PythonSdkType.getPythonExecutable(root);
+  }
+
+  protected String getEnvType() {
+    return "local";
+  }
+
+  private static boolean isSuitableForTask(List<String> tags, PyTestTask task) {
+    return isSuitableForTags(tags, task.getTags());
+  }
+
+  public static boolean isSuitableForTags(List<String> envTags, Set<String> taskTags) {
+    Set<String> necessaryTags = Sets.newHashSet(taskTags);
+
+    for (String tag : envTags) {
+      necessaryTags.remove(tag.trim());
+    }
+
+    for (String tag : taskTags) {
+      if (tag.startsWith("-")) { //do not run on envs with that tag
+        if (envTags.contains(tag.substring(1))) {
+          return false;
+        }
+        necessaryTags.remove(tag);
+      }
+    }
+
+    return necessaryTags.isEmpty();
+  }
+
+
+  protected static boolean isJython(@NotNull String sdkHome) {
+    return sdkHome.toLowerCase().contains("jython");
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/PyEnvTestCase.java b/python/testSrc/com/jetbrains/env/PyEnvTestCase.java
new file mode 100644 (file)
index 0000000..5f07b29
--- /dev/null
@@ -0,0 +1,201 @@
+package com.jetbrains.env.community;
+
+import com.google.common.collect.Lists;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.testFramework.UsefulTestCase;
+import com.intellij.util.SystemProperties;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.python.fixtures.PyTestCase;
+import org.hamcrest.Matchers;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Assume;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * @author traff
+ */
+public abstract class PyEnvTestCase extends UsefulTestCase {
+  private static final Logger LOG = Logger.getInstance(PyEnvTestCase.class.getName());
+
+  private static final String TAGS_FILE = "tags.txt";
+  private static final String PYCHARM_PYTHON_ENVS = "PYCHARM_PYTHON_ENVS";
+  private static final String PYCHARM_PYTHON_VIRTUAL_ENVS = "PYCHARM_PYTHON_VIRTUAL_ENVS";
+
+  protected static final boolean IS_ENV_CONFIGURATION = System.getProperty("pycharm.env") != null;
+
+
+  public static final boolean RUN_REMOTE = SystemProperties.getBooleanProperty("pycharm.run_remote", false);
+
+  public static final boolean RUN_LOCAL = SystemProperties.getBooleanProperty("pycharm.run_local", true);
+
+  /**
+   * Tags that should exist between all tags, available on all interpreters for test to run.
+   * See {@link #PyEnvTestCase(String...)}
+   */
+  @Nullable
+  private final String[] myRequiredTags;
+
+  /**
+   * @param requiredTags tags that should exist on some interpreter for this test to run.
+   *                     if some of these tags do not exist on any interpreter, all test methods would be skipped using
+   *                     {@link org.junit.Assume}.
+   *                     See <a href="http://junit.sourceforge.net/javadoc/org/junit/Assume.html">Assume manual</a>.
+   *                     Check [IDEA-122939] and [TW-25043] as well.
+   */
+  @SuppressWarnings("JUnitTestCaseWithNonTrivialConstructors")
+  protected PyEnvTestCase(@NotNull final String... requiredTags) {
+    myRequiredTags = requiredTags.length > 0 ? requiredTags.clone() : null;
+
+    PyTestCase.initPlatformPrefix();
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    if (myRequiredTags != null) { // Ensure all tags exist between available interpreters
+      Assume.assumeThat(
+        "Can't find some tags between all available interpreter, test (all methods) will be skipped",
+        getAvailableTags(),
+        Matchers.hasItems(myRequiredTags)
+      );
+    }
+  }
+
+  /**
+   * @return all tags available between all interpreters
+   */
+  @NotNull
+  private static Collection<String> getAvailableTags() {
+    final Collection<String> allAvailableTags = new HashSet<String>();
+    for (final String pythonRoot : getPythonRoots()) {
+      allAvailableTags.addAll(loadEnvTags(pythonRoot));
+    }
+    return allAvailableTags;
+  }
+
+  @Override
+  protected void invokeTestRunnable(@NotNull final Runnable runnable) throws Exception {
+    if (runInWriteAction()) {
+      UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+        public void run() {
+          ApplicationManager.getApplication().runWriteAction(runnable);
+        }
+      });
+    }
+    else {
+      runnable.run();
+    }
+  }
+
+  @Override
+  protected boolean runInDispatchThread() {
+    return false;
+  }
+
+  protected boolean runInWriteAction() {
+    return false;
+  }
+
+  public void runPythonTest(final PyTestTask testTask) {
+    runTest(testTask, getTestName(false));
+  }
+
+  public void runTest(@NotNull PyTestTask testTask, @NotNull String testName) {
+    if (notEnvConfiguration()) {
+      fail("Running under teamcity but not by Env configuration. Skipping.");
+      return;
+    }
+
+    List<String> roots = getPythonRoots();
+
+    if (roots.size() == 0) {
+      String msg = testName +
+                   ": environments are not defined. Skipping. \nSpecify either " +
+                   PYCHARM_PYTHON_ENVS +
+                   " or " +
+                   PYCHARM_PYTHON_VIRTUAL_ENVS +
+                   " environment variable.";
+      LOG.warn(msg);
+      System.out.println(msg);
+      return;
+    }
+
+    doRunTests(testTask, testName, roots);
+  }
+
+  protected void doRunTests(PyTestTask testTask, String testName, List<String> roots) {
+    if (RUN_LOCAL) {
+      PyEnvTaskRunner taskRunner = new PyEnvTaskRunner(roots);
+
+      taskRunner.runTask(testTask, testName);
+    }
+  }
+
+
+  public static boolean notEnvConfiguration() {
+    return UsefulTestCase.IS_UNDER_TEAMCITY && !IS_ENV_CONFIGURATION;
+  }
+
+  public static List<String> getPythonRoots() {
+    List<String> roots = Lists.newArrayList();
+
+    String envs = System.getenv(PYCHARM_PYTHON_ENVS);
+    if (envs != null) {
+      roots.addAll(Lists.newArrayList(envs.split(File.pathSeparator)));
+    }
+
+    String virtualEnvs = System.getenv(PYCHARM_PYTHON_VIRTUAL_ENVS);
+
+    if (virtualEnvs != null) {
+      roots.addAll(readVirtualEnvRoots(virtualEnvs));
+    }
+    return roots;
+  }
+
+  protected static List<String> readVirtualEnvRoots(@NotNull String envs) {
+    List<String> result = Lists.newArrayList();
+    String[] roots = envs.split(File.pathSeparator);
+    for (String root : roots) {
+      File virtualEnvRoot = new File(root);
+      File[] virtualenvs = virtualEnvRoot.listFiles();
+      if (virtualenvs != null) {
+        for (File f : virtualenvs) {
+          result.add(f.getAbsolutePath());
+        }
+      } else {
+        LOG.error(root + " is not a directory of doesn't exist");
+      }
+    }
+
+    return result;
+  }
+
+  public static List<String> loadEnvTags(String env) {
+    List<String> envTags;
+
+    try {
+      File parent = new File(env);
+      if (parent.isFile()) {
+        parent = parent.getParentFile();
+      }
+      envTags = com.intellij.openapi.util.io.FileUtil.loadLines(new File(parent, TAGS_FILE));
+    }
+    catch (IOException e) {
+      envTags = Lists.newArrayList();
+    }
+    return envTags;
+  }
+
+  public static String joinStrings(Collection<String> roots, String rootsName) {
+    return roots.size() > 0 ? rootsName + StringUtil.join(roots, ", ") + "\n" : "";
+  }
+}
+
diff --git a/python/testSrc/com/jetbrains/env/PyExecutionFixtureTestTask.java b/python/testSrc/com/jetbrains/env/PyExecutionFixtureTestTask.java
new file mode 100644 (file)
index 0000000..2c48c1b
--- /dev/null
@@ -0,0 +1,174 @@
+package com.jetbrains.env.community;
+
+import com.google.common.collect.Lists;
+import com.intellij.execution.process.ProcessHandler;
+import com.intellij.ide.util.projectWizard.EmptyModuleBuilder;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.module.ModuleTypeManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.testFramework.LightProjectDescriptor;
+import com.intellij.testFramework.builders.ModuleFixtureBuilder;
+import com.intellij.testFramework.fixtures.*;
+import com.intellij.testFramework.fixtures.impl.ModuleFixtureBuilderImpl;
+import com.intellij.testFramework.fixtures.impl.ModuleFixtureImpl;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.python.PythonModuleTypeBase;
+import com.jetbrains.python.fixtures.PyProfessionalTestCase;
+import com.jetbrains.python.sdk.InvalidSdkException;
+import com.jetbrains.python.sdkTools.PyTestSdkTools;
+import com.jetbrains.python.sdkTools.SdkCreationType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Assert;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author traff
+ */
+public abstract class PyExecutionFixtureTestTask extends PyTestTask {
+  public static final int NORMAL_TIMEOUT = 30000;
+  public static final int LONG_TIMEOUT = 120000;
+  protected int myTimeout = NORMAL_TIMEOUT;
+  protected CodeInsightTestFixture myFixture;
+
+  public Project getProject() {
+    return myFixture.getProject();
+  }
+
+  public void useNormalTimeout() {
+    myTimeout = NORMAL_TIMEOUT;
+  }
+
+  public void useLongTimeout() {
+    myTimeout = LONG_TIMEOUT;
+  }
+
+  public void setUp(final String testName) throws Exception {
+    initFixtureBuilder();
+
+    final TestFixtureBuilder<IdeaProjectTestFixture> fixtureBuilder = IdeaTestFixtureFactory.getFixtureFactory().createFixtureBuilder(
+      testName);
+
+    myFixture = IdeaTestFixtureFactory.getFixtureFactory().createCodeInsightFixture(fixtureBuilder.getFixture());
+
+    UIUtil.invokeAndWaitIfNeeded(
+      new Runnable() {
+        @Override
+        public void run() {
+          ModuleFixtureBuilder moduleFixtureBuilder = fixtureBuilder.addModule(MyModuleFixtureBuilder.class);
+          moduleFixtureBuilder.addSourceContentRoot(myFixture.getTempDirPath());
+          moduleFixtureBuilder.addSourceContentRoot(getTestDataPath());
+          final List<String> contentRoots = getContentRoots();
+          for (String contentRoot : contentRoots) {
+            moduleFixtureBuilder.addContentRoot(getTestDataPath() + contentRoot);
+          }
+        }
+      }
+    );
+
+
+    myFixture.setUp();
+    myFixture.setTestDataPath(getTestDataPath());
+  }
+
+  protected List<String> getContentRoots() {
+    return Lists.newArrayList();
+  }
+
+  protected String getTestDataPath() {
+    return PyProfessionalTestCase.getProfessionalTestDataPath();
+  }
+
+  protected void initFixtureBuilder() {
+    IdeaTestFixtureFactory.getFixtureFactory().registerFixtureBuilder(MyModuleFixtureBuilder.class, MyModuleFixtureBuilderImpl.class);
+  }
+
+  public void tearDown() throws Exception {
+    if (myFixture != null) {
+      myFixture.tearDown();
+      myFixture = null;
+    }
+  }
+
+  @Nullable
+  protected LightProjectDescriptor getProjectDescriptor() {
+    return null;
+  }
+
+  protected void disposeProcess(ProcessHandler h) throws InterruptedException {
+    h.destroyProcess();
+    if (!waitFor(h)) {
+      new Throwable("Can't stop process").printStackTrace();
+    }
+  }
+
+  protected boolean waitFor(ProcessHandler p) throws InterruptedException {
+    return p.waitFor(myTimeout);
+  }
+
+  protected boolean waitFor(@NotNull Semaphore s) throws InterruptedException {
+    return waitFor(s, myTimeout);
+  }
+
+  protected static boolean waitFor(@NotNull Semaphore s, long timeout) throws InterruptedException {
+    return s.tryAcquire(timeout, TimeUnit.MILLISECONDS);
+  }
+
+  public static class MyModuleFixtureBuilderImpl extends ModuleFixtureBuilderImpl<ModuleFixture> implements MyModuleFixtureBuilder {
+    public MyModuleFixtureBuilderImpl(TestFixtureBuilder<? extends IdeaProjectTestFixture> fixtureBuilder) {
+      super(new PlatformPythonModuleType(), fixtureBuilder);
+    }
+
+    @Override
+    protected ModuleFixture instantiateFixture() {
+      return new ModuleFixtureImpl(this);
+    }
+  }
+
+  public static class PlatformPythonModuleType extends PythonModuleTypeBase<EmptyModuleBuilder> {
+    @NotNull
+    public static PlatformPythonModuleType getInstance() {
+      return (PlatformPythonModuleType)ModuleTypeManager.getInstance().findByID(PYTHON_MODULE);
+    }
+
+
+    @NotNull
+    @Override
+    public EmptyModuleBuilder createModuleBuilder() {
+      return new EmptyModuleBuilder() {
+        @Override
+        public ModuleType getModuleType() {
+          return getInstance();
+        }
+      };
+    }
+  }
+
+
+  /**
+   * Creates SDK by its path
+   *
+   * @param sdkHome         path to sdk (probably obtained by {@link #runTestOn(String)})
+   * @param sdkCreationType SDK creation strategy (see {@link com.jetbrains.python.sdkTools.SdkCreationType} doc)
+   * @return sdk
+   */
+  @NotNull
+  protected Sdk createTempSdk(@NotNull final String sdkHome, @NotNull final SdkCreationType sdkCreationType)
+    throws InvalidSdkException, IOException {
+    final VirtualFile sdkHomeFile = LocalFileSystem.getInstance().findFileByPath(sdkHome);
+    Assert.assertNotNull("Interpreter file not found: " + sdkHome, sdkHomeFile);
+    return PyTestSdkTools.createTempSdk(sdkHomeFile, sdkCreationType, myFixture.getModule());
+  }
+
+
+  public interface MyModuleFixtureBuilder extends ModuleFixtureBuilder<ModuleFixture> {
+
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/PyTestTask.java b/python/testSrc/com/jetbrains/env/PyTestTask.java
new file mode 100644 (file)
index 0000000..61e81d0
--- /dev/null
@@ -0,0 +1,81 @@
+package com.jetbrains.env.community;
+
+import com.google.common.collect.Sets;
+import com.intellij.openapi.util.io.FileUtil;
+
+import java.util.Set;
+
+/**
+ * @author traff
+ */
+public abstract class PyTestTask {
+  private String myWorkingFolder;
+  private String myScriptName;
+  private String myScriptParameters;
+
+  public void setWorkingFolder(String workingFolder) {
+    myWorkingFolder = workingFolder;
+  }
+
+  public void setScriptName(String scriptName) {
+    myScriptName = scriptName;
+  }
+
+  public void setScriptParameters(String scriptParameters) {
+    myScriptParameters = scriptParameters;
+  }
+
+  public void setUp(String testName) throws Exception {
+  }
+
+  public void tearDown() throws Exception {
+  }
+
+  /**
+   * Run test on certain SDK path.
+   * To create SDK from path, use {@link PyExecutionFixtureTestTask#createTempSdk(String, com.jetbrains.python.sdkTools.SdkCreationType)}
+   *
+   * @param sdkHome sdk path
+   */
+  public abstract void runTestOn(String sdkHome) throws Exception;
+
+  public void before() throws Exception {
+  }
+
+  public void testing() throws Exception {
+  }
+
+  public void after() throws Exception {
+  }
+
+  public void useNormalTimeout() {
+  }
+
+  public void useLongTimeout() {
+  }
+
+  public String getScriptName() {
+    return myScriptName;
+  }
+
+  public String getScriptPath() {
+    return getFilePath(myScriptName);
+  }
+
+  public String getFilePath(String scriptName) {
+    return FileUtil
+      .toSystemDependentName(myWorkingFolder.endsWith("/") ? myWorkingFolder + scriptName : myWorkingFolder + "/" + scriptName);
+  }
+
+  public String getScriptParameters() {
+    return myScriptParameters;
+  }
+
+  public String getWorkingFolder() {
+    return myWorkingFolder;
+  }
+
+  public Set<String> getTags() {
+    return Sets.newHashSet();
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/IPythonConsoleTest.java b/python/testSrc/com/jetbrains/env/python/IPythonConsoleTest.java
new file mode 100644 (file)
index 0000000..a67503e
--- /dev/null
@@ -0,0 +1,100 @@
+package com.jetbrains.env.community.python;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableSet;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiErrorElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.python.console.PyConsoleTask;
+import org.hamcrest.Matchers;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author traff
+ */
+public class IPythonConsoleTest extends PyEnvTestCase {
+  public void testQuestion() throws Exception {
+    runPythonTest(new IPythonTask() {
+      @Override
+      public void testing() throws Exception {
+        exec("import multiprocessing");
+        exec("multiprocessing?");
+        waitForOutput("Type:", "module");
+      }
+    });
+  }
+
+  public void testParsing() throws Exception {
+    runPythonTest(new IPythonTask() {
+      @Override
+      public void testing() throws Exception {
+        waitForReady();
+        addTextToEditor("sys?");
+        ApplicationManager.getApplication().runReadAction(new Runnable() {
+          @Override
+          public void run() {
+            PsiFile psi =
+              PsiDocumentManager.getInstance(getProject())
+                .getPsiFile(getConsoleView().getLanguageConsole().getConsoleEditor().getDocument());
+            Assert.assertThat("No errors expected", getErrors(psi), Matchers.empty());
+          }
+        });
+      }
+    });
+  }
+
+  @NotNull
+  private static Collection<String> getErrors(PsiFile psi) { //TODO: NotNull?
+    if (!PsiTreeUtil.hasErrorElements(psi)) {
+      return Collections.emptyList();
+    }
+
+    return Collections2.transform(PsiTreeUtil.findChildrenOfType(psi, PsiErrorElement.class), new Function<PsiErrorElement, String>() {
+      @Override
+      public String apply(PsiErrorElement input) {
+        return input.getErrorDescription();
+      }
+    });
+  }
+
+  public void testParsingNoIPython() throws Exception {
+    runPythonTest(new IPythonTask() {
+      @Override
+      public void testing() throws Exception {
+        waitForReady();
+        addTextToEditor("sys?");
+        ApplicationManager.getApplication().runReadAction(new Runnable() {
+          @Override
+          public void run() {
+            PsiFile psi =
+              PsiDocumentManager.getInstance(getProject()).getPsiFile(getConsoleView().getLanguageConsole().getConsoleEditor().getDocument());
+            //TreeUtil.ensureParsed(psi.getNode());
+            assertTrue(PsiTreeUtil.hasErrorElements(psi));
+          }
+        });
+
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return ImmutableSet.of("-ipython");
+      }
+    });
+  }
+
+  private static class IPythonTask extends PyConsoleTask {
+    @Override
+    public Set<String> getTags() {
+      return ImmutableSet.of("ipython");
+    }
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/PyPackageRequirementsInspectionTest.java b/python/testSrc/com/jetbrains/env/python/PyPackageRequirementsInspectionTest.java
new file mode 100644 (file)
index 0000000..cda73ca
--- /dev/null
@@ -0,0 +1,83 @@
+package com.jetbrains.env.community.python;
+
+import com.google.common.collect.ImmutableSet;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.ModuleRootModificationUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.testFramework.PsiTestUtil;
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.PyExecutionFixtureTestTask;
+import com.jetbrains.python.PythonTestUtil;
+import com.jetbrains.python.inspections.PyPackageRequirementsInspection;
+import com.jetbrains.python.sdkTools.SdkCreationType;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+/**
+ * @author vlan
+ */
+public class PyPackageRequirementsInspectionTest extends PyEnvTestCase {
+  public static final ImmutableSet<String> TAGS = ImmutableSet.of("requirements");
+
+  public void testPartiallySatisfiedRequirementsTxt() {
+    doTest("test1.py");
+  }
+
+  public void testPartiallySatisfiedSetupPy() {
+    doTest("test1.py");
+  }
+
+  public void testImportsNotInRequirementsTxt() {
+    doTest("test1.py");
+  }
+
+  public void testDuplicateInstallAndTests() {
+    doTest("test1.py");
+  }
+
+  private void doTest(@NotNull final String filename) {
+    final String dir = getTestName(false);
+    runPythonTest(new PyExecutionFixtureTestTask() {
+      @Override
+      protected String getTestDataPath() {
+        return PythonTestUtil.getTestDataPath() + "/inspections/PyPackageRequirementsInspection";
+      }
+
+      @Override
+      public void runTestOn(String sdkHome) throws Exception {
+        myFixture.enableInspections(PyPackageRequirementsInspection.class);
+        final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.SDK_PACKAGES_ONLY);
+        final String perSdkDir = Integer.toHexString(System.identityHashCode(sdk));
+        final VirtualFile root = myFixture.copyDirectoryToProject(dir, perSdkDir);
+        assertNotNull(root);
+        final Module module = myFixture.getModule();
+        setupModuleSdk(module, sdk, root);
+        try {
+          final VirtualFile file = root.findFileByRelativePath(filename);
+          assertNotNull(file);
+          edt(new Runnable() {
+            @Override
+            public void run() {
+              myFixture.testHighlighting(true, true, true, file);
+            }
+          });
+        }
+        finally {
+          PsiTestUtil.removeAllRoots(module, sdk);
+        }
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return TAGS;
+      }
+    });
+  }
+
+  private static void setupModuleSdk(@NotNull Module module, @NotNull Sdk sdk, @NotNull VirtualFile root) {
+    ModuleRootModificationUtil.setModuleSdk(module, sdk);
+    PsiTestUtil.addContentRoot(module, root);
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java b/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java
new file mode 100644 (file)
index 0000000..c8e1197
--- /dev/null
@@ -0,0 +1,164 @@
+package com.jetbrains.env.community.python;
+
+import com.google.common.collect.Sets;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.testFramework.UsefulTestCase;
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.PyExecutionFixtureTestTask;
+import com.jetbrains.env.community.PyTestTask;
+import com.jetbrains.python.packaging.*;
+import com.jetbrains.python.psi.LanguageLevel;
+import com.jetbrains.python.sdk.PythonSdkType;
+import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
+import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor;
+import com.jetbrains.python.sdkTools.SdkCreationType;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author vlan
+ */
+public class PyPackagingTest extends PyEnvTestCase {
+  @Override
+  public void runPythonTest(PyTestTask testTask) {
+    if (UsefulTestCase.IS_UNDER_TEAMCITY && SystemInfo.isWindows) {
+      return; //Don't run under Windows as after deleting from created virtualenvs original interpreter got spoiled
+    }
+
+    super.runPythonTest(testTask);
+  }
+
+  public void testGetPackages() {
+    runPythonTest(new PyPackagingTestTask() {
+      @Override
+      public void runTestOn(String sdkHome) throws Exception {
+        final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.EMPTY_SDK);
+        List<PyPackage> packages = null;
+        try {
+          packages = ((PyPackageManagerImpl)PyPackageManager.getInstance(sdk)).getPackages();
+        }
+        catch (PyExternalProcessException e) {
+          final int retcode = e.getRetcode();
+          if (retcode != PyPackageManagerImpl.ERROR_NO_PIP && retcode != PyPackageManagerImpl.ERROR_NO_SETUPTOOLS) {
+            fail(String.format("Error for interpreter '%s': %s", sdk.getHomePath(), e.getMessage()));
+          }
+        }
+        if (packages != null) {
+          assertTrue(packages.size() > 0);
+          for (PyPackage pkg : packages) {
+            assertTrue(pkg.getName().length() > 0);
+          }
+        }
+      }
+    });
+  }
+
+  public void testCreateVirtualEnv() {
+    runPythonTest(new PyPackagingTestTask() {
+      @Override
+      public void runTestOn(String sdkHome) throws Exception {
+        final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.EMPTY_SDK);
+        try {
+          final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk);
+          // virtualenv >= 0.10 supports Python >= 2.6
+          if (languageLevel.isOlderThan(LanguageLevel.PYTHON26)) {
+            return;
+          }
+          final File tempDir = FileUtil.createTempDirectory(getTestName(false), null);
+          final File venvDir = new File(tempDir, "venv");
+          final String venvSdkHome = ((PyPackageManagerImpl)PyPackageManagerImpl.getInstance(sdk)).createVirtualEnv(venvDir.toString(),
+                                                                                                                    false);
+          final Sdk venvSdk = createTempSdk(venvSdkHome, SdkCreationType.EMPTY_SDK);
+          assertNotNull(venvSdk);
+          assertTrue(PythonSdkType.isVirtualEnv(venvSdk));
+          assertInstanceOf(PythonSdkFlavor.getPlatformIndependentFlavor(venvSdk.getHomePath()), VirtualEnvSdkFlavor.class);
+          final List<PyPackage> packages = ((PyPackageManagerImpl)PyPackageManagerImpl.getInstance(venvSdk)).getPackages();
+          final PyPackage setuptools = findPackage("setuptools", packages);
+          assertNotNull(setuptools);
+          assertEquals("setuptools", setuptools.getName());
+          assertEquals(PyPackageManagerImpl.SETUPTOOLS_VERSION, setuptools.getVersion());
+          final PyPackage pip = findPackage("pip", packages);
+          assertNotNull(pip);
+          assertEquals("pip", pip.getName());
+          assertEquals(PyPackageManagerImpl.PIP_VERSION, pip.getVersion());
+        }
+        catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+        catch (PyExternalProcessException e) {
+          throw new RuntimeException(String.format("Error for interpreter '%s': %s", sdk.getHomePath(), e.getMessage()), e);
+        }
+      }
+    });
+  }
+
+  public void testInstallPackage() {
+    runPythonTest(new PyPackagingTestTask() {
+
+      @Override
+      public void runTestOn(String sdkHome) throws Exception {
+        final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.EMPTY_SDK);
+        try {
+          final File tempDir = FileUtil.createTempDirectory(getTestName(false), null);
+          final File venvDir = new File(tempDir, "venv");
+          final String venvSdkHome = ((PyPackageManagerImpl)PyPackageManager.getInstance(sdk)).createVirtualEnv(venvDir.getPath(), false);
+          final Sdk venvSdk = createTempSdk(venvSdkHome, SdkCreationType.EMPTY_SDK);
+          assertNotNull(venvSdk);
+          final PyPackageManagerImpl manager = (PyPackageManagerImpl)PyPackageManager.getInstance(venvSdk);
+          final List<PyPackage> packages1 = manager.getPackages();
+          // TODO: Install Markdown from a local file
+          manager.install(list(PyRequirement.fromString("Markdown<2.2"),
+                               new PyRequirement("httplib2")), Collections.<String>emptyList());
+          final List<PyPackage> packages2 = manager.getPackages();
+          final PyPackage markdown2 = findPackage("Markdown", packages2);
+          assertNotNull(markdown2);
+          assertTrue(markdown2.isInstalled());
+          final PyPackage pip1 = findPackage("pip", packages1);
+          assertNotNull(pip1);
+          assertEquals("pip", pip1.getName());
+          assertEquals(PyPackageManagerImpl.PIP_VERSION, pip1.getVersion());
+          manager.uninstall(list(pip1));
+          final List<PyPackage> packages3 = manager.getPackages();
+          final PyPackage pip2 = findPackage("pip", packages3);
+          assertNull(pip2);
+        }
+        catch (PyExternalProcessException e) {
+          new RuntimeException(String.format("Error for interpreter '%s': %s", sdk.getHomePath(), e.getMessage()), e);
+        }
+        catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+  }
+
+  @Nullable
+  private static PyPackage findPackage(String name, List<PyPackage> packages) {
+    for (PyPackage pkg : packages) {
+      if (name.equals(pkg.getName())) {
+        return pkg;
+      }
+    }
+    return null;
+  }
+
+  private static <T> List<T> list(T... xs) {
+    return Arrays.asList(xs);
+  }
+
+
+  private abstract static class PyPackagingTestTask extends PyExecutionFixtureTestTask {
+    @Override
+    public Set<String> getTags() {
+      return Sets.newHashSet("packaging");
+    }
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/PythonConsoleTest.java b/python/testSrc/com/jetbrains/env/python/PythonConsoleTest.java
new file mode 100644 (file)
index 0000000..534dd13
--- /dev/null
@@ -0,0 +1,130 @@
+package com.jetbrains.env.community.python;
+
+import com.google.common.collect.Sets;
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.python.console.PyConsoleTask;
+import org.junit.Assert;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author traff
+ */
+public class PythonConsoleTest extends PyEnvTestCase {
+  public void testConsolePrint() throws Exception {
+    runPythonTest(new PyConsoleTask() {
+      @Override
+      public void testing() throws Exception {
+        exec("x = 96");
+        exec("x += 1");
+        exec("print(1)");
+        exec("print(x)");
+        waitForOutput("97");
+      }
+    });
+  }
+
+  public void testExecuteMultiline() throws Exception {   //PY-4329
+    runPythonTest(new PyConsoleTask() {
+      @Override
+      public void testing() throws Exception {
+        exec("if True:\n" +
+             "        x=1\n" +
+             "y=x+100\n" +
+             "for i in range(1):\n" +
+             "  print(y)\n");
+        waitForOutput("101");
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return Sets.newHashSet("-jython"); //jython doesn't support multiline execution: http://bugs.jython.org/issue2106
+      }
+    });
+  }
+
+  public void testInterruptAsync() throws Exception {
+    runPythonTest(new PyConsoleTask() {
+      @Override
+      public void testing() throws Exception {
+        exec("import time");
+        execNoWait("for i in range(10000):\n" +
+                   "  print(i)\n" +
+                   "  time.sleep(0.1)");
+        waitForOutput("3\n4\n5");
+        Assert.assertFalse(canExecuteNow());
+        interrupt();
+        waitForFinish();
+        waitForReady();
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return Sets.newHashSet("-iron", "-jython");
+      }
+    });
+  }
+
+  public void testLineByLineInput() throws Exception {
+    runPythonTest(new PyConsoleTask() {
+      @Override
+      public void testing() throws Exception {
+        exec("x = 96");
+        exec("x +=1");
+        exec("if True:");
+        exec("  print(x)");
+        exec("");
+        exec("");
+        waitForOutput("97");
+      }
+    });
+  }
+
+
+  public void testVariablesView() throws Exception {
+    runPythonTest(new PyConsoleTask() {
+      @Override
+      public void testing() throws Exception {
+        exec("x = 1");
+        exec("print(x)");
+        waitForOutput("1");
+        
+        assertTrue("Variable has wrong value", 
+                   hasValue("x", "1"));
+      }
+    });
+  }
+
+  public void testCompoundVariable() throws Exception {
+    runPythonTest(new PyConsoleTask() {
+      @Override
+      public void testing() throws Exception {
+        exec("x = [1, 2, 3]");
+        exec("print(x)");
+        waitForOutput("[1, 2, 3]");
+
+        List<String> values = getCompoundValueChildren(getValue("x"));
+        Collections.sort(values);
+        assertContainsElements(values, "1", "2", "3", "3");
+      }
+    });
+  }
+
+  public void testChangeVariable() throws Exception {
+    runPythonTest(new PyConsoleTask() {
+      @Override
+      public void testing() throws Exception {
+        exec("x = 1");
+        exec("print(x)");
+        waitForOutput("1");
+        
+        setValue("x", "2");
+
+        assertTrue("Variable has wrong value",
+                   hasValue("x", "2"));
+      }
+    });
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java
new file mode 100644 (file)
index 0000000..9db5986
--- /dev/null
@@ -0,0 +1,579 @@
+package com.jetbrains.env.community.python;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.intellij.testFramework.fixtures.IdeaProjectTestFixture;
+import com.intellij.xdebugger.XDebuggerTestUtil;
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.python.debug.PyDebuggerTask;
+import com.jetbrains.env.community.ut.PyUnitTestTask;
+import com.jetbrains.python.PythonHelpersLocator;
+import com.jetbrains.python.console.pydev.PydevCompletionVariant;
+import com.jetbrains.python.debugger.PyDebuggerException;
+import com.jetbrains.python.debugger.PyExceptionBreakpointProperties;
+import com.jetbrains.python.debugger.PyExceptionBreakpointType;
+import com.jetbrains.python.debugger.pydev.ProcessDebugger;
+import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author traff
+ */
+
+public class PythonDebuggerTest extends PyEnvTestCase {
+  public void testBreakpointStopAndEval() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test1.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 3);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+
+        eval("i").hasValue("0");
+
+        resume();
+
+        waitForPause();
+
+        eval("i").hasValue("1");
+
+        resume();
+
+        waitForPause();
+
+        eval("i").hasValue("2");
+      }
+    });
+  }
+
+  public void testDebugger() {
+    runPythonTest(new PyUnitTestTask("", "test_debug.py") {
+      @Override
+      protected String getTestDataPath() {
+        return PythonHelpersLocator.getPythonCommunityPath() + "/helpers/pydev";
+      }
+
+      @Override
+      public void after() {
+        allTestsPassed();
+      }
+    });
+  }
+
+  public void testConditionalBreakpoint() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test1.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 3);
+        XDebuggerTestUtil.setBreakpointCondition(getProject(), 3, "i == 1 or i == 11 or i == 111");
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+
+        eval("i").hasValue("1");
+
+        resume();
+
+        waitForPause();
+
+        eval("i").hasValue("11");
+
+        resume();
+
+        waitForPause();
+
+        eval("i").hasValue("111");
+      }
+    });
+  }
+
+  public void testDebugConsole() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test1.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 3);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+
+        eval("i").hasValue("0");
+
+        resume();
+
+        waitForPause();
+
+        consoleExec("'i=%d'%i");
+
+        waitForOutput("'i=1'");
+
+        consoleExec("x");
+
+        waitForOutput("name 'x' is not defined");
+
+        consoleExec("1-;");
+
+        waitForOutput("SyntaxError");
+
+        resume();
+      }
+
+      private void consoleExec(String command) {
+        myDebugProcess.consoleExec(command, new ProcessDebugger.DebugCallback<String>() {
+          @Override
+          public void ok(String value) {
+
+          }
+
+          @Override
+          public void error(PyDebuggerException exception) {
+          }
+        });
+      }
+    });
+  }
+
+
+  public void testDebugCompletion() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test4.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 3);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+
+        List<PydevCompletionVariant> list = myDebugProcess.getCompletions("xvalu");
+        assertEquals(2, list.size());
+      }
+    });
+  }
+
+  public void testBreakpointLogExpression() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test1.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 3);
+        XDebuggerTestUtil.setBreakpointLogExpression(getProject(), 3, "'i = %d'%i");
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        resume();
+        waitForOutput("i = 1");
+      }
+    });
+  }
+
+  public void testStepOver() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test2.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 5);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        stepOver();
+        waitForPause();
+        stepOver();
+        waitForPause();
+        eval("z").hasValue("2");
+      }
+    });
+  }
+
+  public void testStepInto() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test2.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 5);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        stepInto();
+        waitForPause();
+        eval("x").hasValue("1");
+        stepOver();
+        waitForPause();
+        eval("y").hasValue("3");
+        stepOver();
+        waitForPause();
+        eval("z").hasValue("1");
+      }
+    });
+  }
+
+  public void testSmartStepInto() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test3.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 14);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        smartStepInto("foo");
+        waitForPause();
+        stepOver();
+        waitForPause();
+        eval("y").hasValue("4");
+      }
+    });
+  }
+
+  public void testSmartStepInto2() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test3.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 18);
+        toggleBreakpoint(getScriptPath(), 25);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        toggleBreakpoint(getScriptPath(), 18);
+        smartStepInto("foo");
+        waitForPause();
+        eval("a.z").hasValue("1");
+      }
+    });
+  }
+
+  public void testInput() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_input.py") {
+      @Override
+      public void before() throws Exception {
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForOutput("print command >");
+        input("GO!");
+        waitForOutput("command was GO!");
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return ImmutableSet.of("-jython"); //can't run on jython
+      }
+    });
+  }
+
+  public void testRunToLine() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_runtoline.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 1);
+        toggleBreakpoint(getScriptPath(), 7);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        eval("x").hasValue("0");
+        runToLine(4);
+        eval("x").hasValue("1");
+        resume();
+        waitForPause();
+        eval("x").hasValue("12");
+        resume();
+
+        waitForOutput("x = 12");
+      }
+    });
+  }
+
+  private static void addExceptionBreakpoint(IdeaProjectTestFixture fixture, PyExceptionBreakpointProperties properties) {
+    XDebuggerTestUtil.addBreakpoint(fixture.getProject(), PyExceptionBreakpointType.class, properties);
+  }
+
+  public void testExceptionBreakpointOnTerminate() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_exceptbreak.py") {
+      @Override
+      public void before() throws Exception {
+        createExceptionBreak(myFixture, true, false, false);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        eval("__exception__[0].__name__").hasValue("'ZeroDivisionError'");
+        resume();
+        waitForTerminate();
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return ImmutableSet.of("-iron");
+      }
+    });
+  }
+
+  private static void createExceptionBreak(IdeaProjectTestFixture fixture,
+                                           boolean notifyOnTerminate,
+                                           boolean notifyAlways,
+                                           boolean notifyOnFirst) {
+    XDebuggerTestUtil.removeAllBreakpoints(fixture.getProject());
+    XDebuggerTestUtil.setDefaultBreakpointEnabled(fixture.getProject(), PyExceptionBreakpointType.class, false);
+
+    PyExceptionBreakpointProperties properties = new PyExceptionBreakpointProperties("exceptions.ZeroDivisionError");
+    properties.setNotifyOnTerminate(notifyOnTerminate);
+    properties.setNotifyAlways(notifyAlways);
+    properties.setNotifyOnlyOnFirst(notifyOnFirst);
+    addExceptionBreakpoint(fixture, properties);
+    properties = new PyExceptionBreakpointProperties("builtins.ZeroDivisionError"); //for python 3
+    properties.setNotifyOnTerminate(notifyOnTerminate);
+    properties.setNotifyAlways(notifyAlways);
+    properties.setNotifyOnlyOnFirst(notifyOnFirst);
+    addExceptionBreakpoint(fixture, properties);
+  }
+
+  public void testExceptionBreakpointAlways() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_exceptbreak.py") {
+      @Override
+      public void before() throws Exception {
+        createExceptionBreak(myFixture, false, true, false);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        eval("__exception__[0].__name__").hasValue("'ZeroDivisionError'");
+        resume();
+        waitForPause();
+        resume();
+        waitForPause();
+        resume();
+        waitForTerminate();
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return ImmutableSet.of("-pypy"); //TODO: fix it for Pypy
+      }
+    });
+  }
+
+  public void testExceptionBreakpointOnFirstRaise() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_exceptbreak.py") {
+      @Override
+      public void before() throws Exception {
+        createExceptionBreak(myFixture, false, false, true);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        eval("__exception__[0].__name__").hasValue("'ZeroDivisionError'");
+        resume();
+        waitForTerminate();
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return ImmutableSet.of("-iron");
+      }
+    });
+  }
+
+  public void testMultithreading() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_multithread.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 9);
+        toggleBreakpoint(getScriptPath(), 13);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        eval("y").hasValue("2");
+        resume();
+        waitForPause();
+        eval("z").hasValue("102");
+        resume();
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return ImmutableSet.of("-pypy"); //TODO: fix that for PyPy
+      }
+    });
+  }
+
+  public void testEggDebug() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_egg.py") {
+      @Override
+      public void before() throws Exception {
+        String egg = getFilePath("Adder-0.1.egg");
+        toggleBreakpointInEgg(egg, "adder/adder.py", 2);
+        PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(getRunConfiguration().getSdkHome());
+        if (flavor != null) {
+          flavor.initPythonPath(Lists.newArrayList(egg), getRunConfiguration().getEnvs());
+        }
+        else {
+          getRunConfiguration().getEnvs().put("PYTHONPATH", egg);
+        }
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        eval("ret").hasValue("16");
+        resume();
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return ImmutableSet.of("-jython"); //TODO: fix that for Jython if anybody needs it
+      }
+    });
+  }
+
+  public void testStepOverConditionalBreakpoint() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_stepOverCondition.py") {
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 1);
+        toggleBreakpoint(getScriptPath(), 2);
+        XDebuggerTestUtil.setBreakpointCondition(getProject(), 2, "y == 3");
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+        stepOver();
+        waitForPause();
+        eval("y").hasValue("2");
+      }
+    });
+  }
+
+  public void testMultiprocess() throws Exception {
+    runPythonTest(new PyDebuggerTask("/debug", "test_multiprocess.py") {
+      @Override
+      protected void init() {
+        setMultiprocessDebug(true);
+      }
+
+      @Override
+      public void before() throws Exception {
+        toggleBreakpoint(getScriptPath(), 9);
+      }
+
+      @Override
+      public void testing() throws Exception {
+        waitForPause();
+
+        eval("i").hasValue("'Result:OK'");
+
+        resume();
+
+        waitForOutput("Result:OK");
+      }
+
+      @Override
+      public Set<String> getTags() {
+        return Sets.newHashSet("python3");
+      }
+    });
+  }
+
+
+  //TODO: fix me as I don't work properly sometimes (something connected with process termination on agent)
+  //public void testResume() throws Exception {
+  //  runPythonTest(new PyDebuggerTask("/debug", "Test_Resume.py") {
+  //    @Override
+  //    public void before() throws Exception {
+  //      toggleBreakpoint(getScriptPath(), 2);
+  //    }
+  //
+  //    @Override
+  //    public void testing() throws Exception {
+  //      waitForPause();
+  //      eval("x").hasValue("1");
+  //      resume();
+  //      waitForPause();
+  //      eval("x").hasValue("2");
+  //      resume();
+  //    }
+  //  });
+  //}
+
+
+  //TODO: first fix strange hanging of that test
+  //public void testRemoteDebug() throws Exception {
+  //  runPythonTest(new PyRemoteDebuggerTask("/debug", "test_remote.py") {
+  //    @Override
+  //    public void before() throws Exception {
+  //    }
+  //
+  //    @Override
+  //    public void testing() throws Exception {
+  //      waitForPause();
+  //      eval("x").hasValue("0");
+  //      stepOver();
+  //      waitForPause();
+  //      eval("x").hasValue("1");
+  //      stepOver();
+  //      waitForPause();
+  //      eval("x").hasValue("2");
+  //      resume();
+  //    }
+  //
+  //    @Override
+  //    protected void checkOutput(ProcessOutput output) {
+  //      assertEmpty(output.getStderr());
+  //      assertEquals("OK", output.getStdout().trim());
+  //    }
+  //
+  //    @Override
+  //    public void after() throws Exception {
+  //      stopDebugServer();
+  //    }
+  //  });
+  //}
+
+  //TODO: That doesn't work now: case from test_continuation.py and test_continuation2.py are treated differently by interpreter
+  // (first line is executed in first case and last line in second)
+
+  //public void testBreakOnContinuationLine() throws Exception {
+  //  runPythonTest(new PyDebuggerTask("/debug", "test_continuation.py") {
+  //    @Override
+  //    public void before() throws Exception {
+  //      toggleBreakpoint(getScriptPath(), 13);
+  //    }
+  //
+  //    @Override
+  //    public void testing() throws Exception {
+  //      waitForPause();
+  //      eval("x").hasValue("0");
+  //      stepOver();
+  //      waitForPause();
+  //      eval("x").hasValue("1");
+  //      stepOver();
+  //      waitForPause();
+  //      eval("x").hasValue("2");
+  //    }
+  //  });
+  //}
+}
+
diff --git a/python/testSrc/com/jetbrains/env/python/PythonGeneratorTest.java b/python/testSrc/com/jetbrains/env/python/PythonGeneratorTest.java
new file mode 100644 (file)
index 0000000..f28344e
--- /dev/null
@@ -0,0 +1,24 @@
+package com.jetbrains.env.community.python;
+
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.ut.PyUnitTestTask;
+import com.jetbrains.python.PythonHelpersLocator;
+
+/**
+ * @author traff
+ */
+public class PythonGeneratorTest extends PyEnvTestCase{
+  public void testGenerator() {
+    runPythonTest(new PyUnitTestTask("", "test_generator.py") {
+      @Override
+      protected String getTestDataPath() {
+        return PythonHelpersLocator.getPythonCommunityPath() + "/helpers";
+      }
+
+      @Override
+      public void after() {
+        allTestsPassed();
+      }
+    });
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/PythonSkeletonsTest.java b/python/testSrc/com/jetbrains/env/python/PythonSkeletonsTest.java
new file mode 100644 (file)
index 0000000..de34949
--- /dev/null
@@ -0,0 +1,202 @@
+package com.jetbrains.env.community.python;
+
+import com.google.common.collect.ImmutableSet;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.PyExecutionFixtureTestTask;
+import com.jetbrains.env.community.PyTestTask;
+import com.jetbrains.python.PythonFileType;
+import com.jetbrains.python.PythonTestUtil;
+import com.jetbrains.python.documentation.PythonDocumentationProvider;
+import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.impl.PyBuiltinCache;
+import com.jetbrains.python.psi.resolve.PythonSdkPathCache;
+import com.jetbrains.python.psi.types.PyType;
+import com.jetbrains.python.psi.types.TypeEvalContext;
+import com.jetbrains.python.sdk.PythonSdkType;
+import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher;
+import com.jetbrains.python.sdk.skeletons.SkeletonVersionChecker;
+import com.jetbrains.python.sdkTools.SdkCreationType;
+import com.jetbrains.python.toolbox.Maybe;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Heavyweight integration tests of skeletons of Python binary modules.
+ * <p/>
+ * An environment test environment must have a 'skeletons' tag in order to be compatible with this test case. No specific packages are
+ * required currently. Both Python 2 and Python 3 are OK. All platforms are OK. At least one Python 2.6+ environment is required.
+ *
+ * @author vlan
+ */
+public class PythonSkeletonsTest extends PyEnvTestCase {
+  public static final ImmutableSet<String> TAGS = ImmutableSet.of("skeletons");
+
+  public void testBuiltins() {
+    runTest(new SkeletonsTask() {
+      @Override
+      public void runTestOn(@NotNull Sdk sdk) {
+        // Check the builtin skeleton header
+        final Project project = myFixture.getProject();
+        final PyFile builtins = PyBuiltinCache.getBuiltinsForSdk(project, sdk);
+        assertNotNull(builtins);
+        final VirtualFile virtualFile = builtins.getVirtualFile();
+        assertNotNull(virtualFile);
+        assertTrue(virtualFile.isInLocalFileSystem());
+        final String path = virtualFile.getPath();
+        final PySkeletonRefresher.SkeletonHeader header = PySkeletonRefresher.readSkeletonHeader(new File(path));
+        assertNotNull(header);
+        final int version = header.getVersion();
+        assertTrue("Header version must be > 0, currently it is " + version, version > 0);
+        assertEquals(SkeletonVersionChecker.BUILTIN_NAME, header.getBinaryFile());
+
+        // Run inspections on a file that uses builtins
+        myFixture.configureByFile(getTestName(false) + ".py");
+
+
+        PsiFile expr = myFixture.getFile();
+
+        final Module module = ModuleUtil.findModuleForPsiElement(expr);
+
+        final Sdk sdkFromModule = PythonSdkType.findPythonSdk(module);
+        assertNotNull(sdkFromModule);
+
+        final Sdk sdkFromPsi = PyBuiltinCache.findSdkForFile(expr.getContainingFile());
+        final PyFile builtinsFromSdkCache = PythonSdkPathCache.getInstance(project, sdkFromPsi).getBuiltins().getBuiltinsFile();
+        assertNotNull(builtinsFromSdkCache);
+        assertEquals(builtins, builtinsFromSdkCache);
+
+        final PyFile builtinsFromPsi = PyBuiltinCache.getInstance(expr).getBuiltinsFile();
+        assertNotNull(builtinsFromPsi);
+        assertEquals(builtins, builtinsFromPsi);
+
+        myFixture.enableInspections(PyUnresolvedReferencesInspection.class);
+        edt(new Runnable() {
+          @Override
+          public void run() {
+            myFixture.checkHighlighting(true, false, false);
+          }
+        });
+      }
+    });
+  }
+
+  // PY-4349
+  public void testFakeNamedTuple() {
+    runTest(new SkeletonsTask() {
+      @Override
+      protected void runTestOn(@NotNull Sdk sdk) {
+        final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk);
+        // Named tuples have been introduced in Python 2.6
+        if (languageLevel.isOlderThan(LanguageLevel.PYTHON26)) {
+          return;
+        }
+
+        // XXX: A workaround for invalidating VFS cache with the test file copied to our temp directory
+        LocalFileSystem.getInstance().refresh(false);
+
+        // Run inspections on code that uses named tuples
+        myFixture.configureByFile(getTestName(false) + ".py");
+        myFixture.enableInspections(PyUnresolvedReferencesInspection.class);
+
+        edt(new Runnable() {
+          @Override
+          public void run() {
+            myFixture.checkHighlighting(true, false, false);
+          }
+        });
+      }
+    });
+  }
+
+  public void testKnownPropertiesTypes() {
+    runTest(new SkeletonsTask() {
+      @Override
+      protected void runTestOn(@NotNull Sdk sdk) {
+        myFixture.configureByText(PythonFileType.INSTANCE,
+                                  "expr = slice(1, 2).start\n");
+        final PyExpression expr = myFixture.findElementByText("expr", PyExpression.class);
+        final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getFile());
+        ApplicationManager.getApplication().runReadAction(new Runnable() {
+          @Override
+          public void run() {
+            final PyType type = context.getType(expr);
+            final String actualType = PythonDocumentationProvider.getTypeName(type, context);
+            assertEquals("int", actualType);
+          }
+        });
+      }
+    });
+  }
+
+  // PY-9797
+  public void testReadWriteDeletePropertyDefault() {
+    runTest(new SkeletonsTask() {
+      @Override
+      protected void runTestOn(@NotNull Sdk sdk) {
+        final LanguageLevel languageLevel = PythonSdkType.getLanguageLevelForSdk(sdk);
+        // We rely on int.real property that is not explicitly annotated in the skeletons generator
+        if (languageLevel.isOlderThan(LanguageLevel.PYTHON26)) {
+          return;
+        }
+        final Project project = myFixture.getProject();
+        final PyFile builtins = PyBuiltinCache.getBuiltinsForSdk(project, sdk);
+        assertNotNull(builtins);
+        ApplicationManager.getApplication().runReadAction(new Runnable() {
+          @Override
+          public void run() {
+            final PyClass cls = builtins.findTopLevelClass("int");
+            assertNotNull(cls);
+            final Property prop = cls.findProperty("real", true);
+            assertNotNull(prop);
+            assertIsNotNull(prop.getGetter());
+            assertIsNotNull(prop.getSetter());
+            assertIsNotNull(prop.getDeleter());
+          }
+        });
+      }
+
+      private void assertIsNotNull(Maybe<Callable> accessor) {
+        if (accessor.isDefined()) {
+          assertNotNull(accessor.valueOrNull());
+        }
+      }
+    });
+  }
+
+
+  private void runTest(@NotNull PyTestTask task) {
+    runPythonTest(task);
+  }
+
+
+  private abstract class SkeletonsTask extends PyExecutionFixtureTestTask {
+    @Override
+    protected String getTestDataPath() {
+      return PythonTestUtil.getTestDataPath() + "/skeletons/";
+    }
+
+    @Override
+    public void runTestOn(String sdkHome) throws Exception {
+      final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.SDK_PACKAGES_AND_SKELETONS);
+      runTestOn(sdk);
+    }
+
+    @Override
+    public Set<String> getTags() {
+      return TAGS;
+    }
+
+    protected abstract void runTestOn(@NotNull Sdk sdk);
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/console/PyConsoleTask.java b/python/testSrc/com/jetbrains/env/python/console/PyConsoleTask.java
new file mode 100644 (file)
index 0000000..d43ce0a
--- /dev/null
@@ -0,0 +1,380 @@
+package com.jetbrains.env.community.python.console;
+
+import com.google.common.collect.Lists;
+import com.intellij.execution.console.LanguageConsoleView;
+import com.intellij.execution.process.ProcessAdapter;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.xdebugger.frame.XValueChildrenList;
+import com.jetbrains.env.community.PyExecutionFixtureTestTask;
+import com.jetbrains.python.console.*;
+import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
+import com.jetbrains.python.debugger.PyDebugValue;
+import com.jetbrains.python.debugger.PyDebuggerException;
+import com.jetbrains.python.sdkTools.SdkCreationType;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+import java.util.List;
+import java.util.concurrent.Semaphore;
+
+/**
+ * @author traff
+ */
+public class PyConsoleTask extends PyExecutionFixtureTestTask {
+  private boolean myProcessCanTerminate;
+
+  protected PyConsoleProcessHandler myProcessHandler;
+  protected PydevConsoleCommunication myCommunication;
+
+  private boolean shouldPrintOutput = false;
+  private PythonConsoleView myConsoleView;
+  private Semaphore mySemaphore;
+  private Semaphore mySemaphore0;
+  private PydevConsoleExecuteActionHandler myExecuteHandler;
+
+  public PyConsoleTask() {
+    setWorkingFolder(getTestDataPath());
+  }
+
+  public PythonConsoleView getConsoleView() {
+    return myConsoleView;
+  }
+
+  @Override
+  public void setUp(final String testName) throws Exception {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          if (myFixture == null) {
+            PyConsoleTask.super.setUp(testName);
+          }
+        }
+        catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+  }
+
+  @NotNull
+  protected String output() {
+    return myConsoleView.getConsole().getHistoryViewer().getDocument().getText();
+  }
+
+  public void setProcessCanTerminate(boolean processCanTerminate) {
+    myProcessCanTerminate = processCanTerminate;
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          if (myConsoleView != null) {
+            disposeConsole();
+          }
+          PyConsoleTask.super.tearDown();
+        }
+        catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+  }
+
+  private void disposeConsole() throws InterruptedException {
+    if (myCommunication != null) {
+      UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+        @Override
+        public void run() {
+          try {
+            myCommunication.close();
+          }
+          catch (Exception e) {
+            e.printStackTrace();
+          }
+          myCommunication = null;
+        }
+      });
+    }
+
+    disposeConsoleProcess();
+
+    if (myConsoleView != null) {
+      new WriteAction() {
+        @Override
+        protected void run(@NotNull Result result) throws Throwable {
+          Disposer.dispose(myConsoleView);
+          myConsoleView = null;
+        }
+      }.execute();
+    }
+  }
+
+  @Override
+  public void runTestOn(final String sdkHome) throws Exception {
+    final Project project = getProject();
+
+    final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.EMPTY_SDK);
+
+    setProcessCanTerminate(false);
+
+    PydevConsoleRunner consoleRunner = PydevConsoleRunner.create(project, sdk, PyConsoleType.PYTHON, getWorkingFolder());
+    before();
+
+    mySemaphore0 = new Semaphore(0);
+
+    consoleRunner.addConsoleListener(new PydevConsoleRunner.ConsoleListener() {
+      @Override
+      public void handleConsoleInitialized(LanguageConsoleView consoleView) {
+        mySemaphore0.release();
+      }
+    });
+
+    consoleRunner.run();
+
+    waitFor(mySemaphore0);
+
+    mySemaphore = new Semaphore(0);
+
+    myConsoleView = consoleRunner.getConsoleView();
+    myProcessHandler = (PyConsoleProcessHandler)consoleRunner.getProcessHandler();
+
+    myExecuteHandler = (PydevConsoleExecuteActionHandler)consoleRunner.getConsoleExecuteActionHandler();
+
+    myCommunication = consoleRunner.getPydevConsoleCommunication();
+
+    myCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
+      @Override
+      public void commandExecuted(boolean more) {
+        mySemaphore.release();
+      }
+
+      @Override
+      public void inputRequested() {
+      }
+    });
+
+    myProcessHandler.addProcessListener(new ProcessAdapter() {
+      @Override
+      public void processTerminated(ProcessEvent event) {
+        if (event.getExitCode() != 0 && !myProcessCanTerminate) {
+          Assert.fail("Process terminated unexpectedly\n" + output());
+        }
+      }
+    });
+
+    OutputPrinter myOutputPrinter = null;
+    if (shouldPrintOutput) {
+      myOutputPrinter = new OutputPrinter();
+      myOutputPrinter.start();
+    }
+
+    waitForOutput("PyDev console");
+
+    try {
+      testing();
+      after();
+    }
+    finally {
+      setProcessCanTerminate(true);
+
+      if (myOutputPrinter != null) {
+        myOutputPrinter.stop();
+      }
+
+      disposeConsole();
+    }
+  }
+
+  private void disposeConsoleProcess() throws InterruptedException {
+    myProcessHandler.destroyProcess();
+
+    waitFor(myProcessHandler);
+
+    if (!myProcessHandler.isProcessTerminated()) {
+      if (!waitFor(myProcessHandler)) {
+        if (!myProcessHandler.isProcessTerminated()) {
+          throw new RuntimeException("Cannot stop console process");
+        }
+      }
+    }
+    myProcessHandler = null;
+  }
+
+  /**
+   * Waits until all passed strings appear in output.
+   * If they don't appear in time limit, then exception is raised.
+   *
+   * @param string
+   * @throws InterruptedException
+   */
+  public void waitForOutput(String... string) throws InterruptedException {
+    int count = 0;
+    while (true) {
+      List<String> missing = Lists.newArrayList();
+      String out = output();
+      boolean flag = true;
+      for (String s : string) {
+        if (!out.contains(s)) {
+          flag = false;
+          missing.add(s);
+        }
+      }
+      if (flag) {
+        break;
+      }
+      if (count > 10) {
+        Assert.fail("Strings: <--\n" + StringUtil.join(missing, "\n---\n") + "-->" + "are not present in output.\n" + output());
+      }
+      Thread.sleep(2000);
+      count++;
+    }
+  }
+
+  protected void waitForReady() throws InterruptedException {
+    int count = 0;
+    while (!myExecuteHandler.isEnabled() || !canExecuteNow()) {
+      if (count > 10) {
+        Assert.fail("Console is not ready");
+      }
+      Thread.sleep(300);
+      count++;
+    }
+  }
+
+  protected boolean canExecuteNow() {
+    return myExecuteHandler.canExecuteNow();
+  }
+
+  public void setShouldPrintOutput(boolean shouldPrintOutput) {
+    this.shouldPrintOutput = shouldPrintOutput;
+  }
+
+  private class OutputPrinter {
+    private Thread myThread;
+    private int myLen = 0;
+
+    public void start() {
+      myThread = new Thread(new Runnable() {
+        @Override
+        public void run() {
+          doJob();
+        }
+      });
+      myThread.setDaemon(true);
+      myThread.start();
+    }
+
+    private void doJob() {
+      try {
+        while (true) {
+          printToConsole();
+
+          Thread.sleep(500);
+        }
+      }
+      catch (Exception ignored) {
+      }
+    }
+
+    private synchronized void printToConsole() {
+      String s = output();
+      if (s.length() > myLen) {
+        System.out.print(s.substring(myLen));
+      }
+      myLen = s.length();
+    }
+
+    public void stop() {
+      printToConsole();
+      myThread.interrupt();
+    }
+  }
+
+  protected void exec(String command) throws InterruptedException {
+    waitForReady();
+    int p = mySemaphore.availablePermits();
+    myConsoleView.executeInConsole(command);
+    mySemaphore.acquire(p + 1);
+    //waitForOutput(">>> " + command);
+  }
+
+  protected boolean hasValue(String varName, String value) throws PyDebuggerException {
+    PyDebugValue val = getValue(varName);
+    return val != null && value.equals(val.getValue());
+  }
+
+  protected void setValue(String varName, String value) throws PyDebuggerException {
+    PyDebugValue val = getValue(varName);
+    myCommunication.changeVariable(val, value);
+  }
+  
+  protected PyDebugValue getValue(String varName) throws PyDebuggerException {
+    XValueChildrenList l = myCommunication.loadFrame();
+
+    if (l == null) {
+      return null;
+    }
+    for (int i = 0; i < l.size(); i++) {
+      String name = l.getName(i);
+      if (varName.equals(name)) {
+        return (PyDebugValue)l.getValue(i);
+      }
+    }
+    
+    return null;
+  }
+  
+  protected List<String> getCompoundValueChildren(PyDebugValue value) throws PyDebuggerException {
+    XValueChildrenList list = myCommunication.loadVariable(value);
+    List<String> result = Lists.newArrayList();
+    for (int i = 0; i<list.size(); i++) {
+      result.add(((PyDebugValue)list.getValue(i)).getValue());
+    }
+    return result;
+  }
+
+  protected void input(String text) {
+    myConsoleView.executeInConsole(text);
+  }
+
+  protected void waitForFinish() throws InterruptedException {
+    waitFor(mySemaphore);
+  }
+
+  protected void execNoWait(final String command) {
+    UIUtil.invokeLaterIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        myConsoleView.executeCode(command, null);
+      }
+    });
+  }
+
+  protected void interrupt() {
+    myCommunication.interrupt();
+  }
+
+
+  public void addTextToEditor(final String text) {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        getConsoleView().getLanguageConsole().setInputText(text);
+        PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
+      }
+    }
+    );
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java b/python/testSrc/com/jetbrains/env/python/debug/PyBaseDebuggerTask.java
new file mode 100644 (file)
index 0000000..b3426fc
--- /dev/null
@@ -0,0 +1,361 @@
+package com.jetbrains.env.community.python.debug;
+
+import com.google.common.collect.Sets;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.xdebugger.*;
+import com.intellij.xdebugger.frame.XValue;
+import com.jetbrains.django.util.VirtualFileUtil;
+import com.jetbrains.env.community.PyExecutionFixtureTestTask;
+import com.jetbrains.python.console.PythonDebugLanguageConsoleView;
+import com.jetbrains.python.debugger.PyDebugProcess;
+import com.jetbrains.python.debugger.PyDebugValue;
+import com.jetbrains.python.debugger.PyDebuggerException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Assert;
+
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+
+/**
+ * @author traff
+ */
+public abstract class PyBaseDebuggerTask extends PyExecutionFixtureTestTask {
+  private Set<Pair<String, Integer>> myBreakpoints = Sets.newHashSet();
+  protected PyDebugProcess myDebugProcess;
+  protected XDebugSession mySession;
+  protected Semaphore myPausedSemaphore;
+  protected Semaphore myTerminateSemaphore;
+  protected boolean shouldPrintOutput = false;
+  protected boolean myProcessCanTerminate;
+
+  protected void waitForPause() throws InterruptedException, InvocationTargetException {
+    Assert.assertTrue("Debugger didn't stopped within timeout\nOutput:" + output(), waitFor(myPausedSemaphore));
+
+    XDebuggerTestUtil.waitForSwing();
+  }
+
+  protected void waitForTerminate() throws InterruptedException, InvocationTargetException {
+    setProcessCanTerminate(true);
+
+    Assert.assertTrue("Debugger didn't terminated within timeout\nOutput:" + output(), waitFor(myTerminateSemaphore));
+    XDebuggerTestUtil.waitForSwing();
+  }
+
+  protected void runToLine(int line) throws InvocationTargetException, InterruptedException {
+    XDebugSession currentSession = XDebuggerManager.getInstance(getProject()).getCurrentSession();
+    XSourcePosition position = currentSession.getCurrentPosition();
+
+
+    currentSession.runToPosition(XDebuggerUtil.getInstance().createPosition(position.getFile(), line), false);
+
+    waitForPause();
+  }
+
+  protected void resume() {
+    XDebugSession currentSession = XDebuggerManager.getInstance(getProject()).getCurrentSession();
+
+    Assert.assertTrue(currentSession.isSuspended());
+    Assert.assertEquals(0, myPausedSemaphore.availablePermits());
+
+    currentSession.resume();
+  }
+
+  protected void stepOver() {
+    XDebugSession currentSession = XDebuggerManager.getInstance(getProject()).getCurrentSession();
+
+    Assert.assertTrue(currentSession.isSuspended());
+    Assert.assertEquals(0, myPausedSemaphore.availablePermits());
+
+    currentSession.stepOver(false);
+  }
+
+  protected void stepInto() {
+    XDebugSession currentSession = XDebuggerManager.getInstance(getProject()).getCurrentSession();
+
+    Assert.assertTrue(currentSession.isSuspended());
+    Assert.assertEquals(0, myPausedSemaphore.availablePermits());
+
+    currentSession.stepInto();
+  }
+
+  protected void smartStepInto(String funcName) {
+    XDebugSession currentSession = XDebuggerManager.getInstance(getProject()).getCurrentSession();
+
+    Assert.assertTrue(currentSession.isSuspended());
+    Assert.assertEquals(0, myPausedSemaphore.availablePermits());
+
+    myDebugProcess.startSmartStepInto(funcName);
+  }
+
+  @NotNull
+  protected String output() {
+    if (mySession != null && mySession.getConsoleView() != null) {
+      PythonDebugLanguageConsoleView pydevConsoleView = (PythonDebugLanguageConsoleView)mySession.getConsoleView();
+      if (pydevConsoleView != null) {
+        return XDebuggerTestUtil.getConsoleText(pydevConsoleView.getTextConsole());
+      }
+    }
+    return "Console output not available.";
+  }
+
+  protected void input(String text) {
+    PrintWriter pw = new PrintWriter(myDebugProcess.getProcessHandler().getProcessInput());
+    pw.println(text);
+    pw.flush();
+  }
+
+  private void outputContains(String substring) {
+    Assert.assertTrue(output().contains(substring));
+  }
+
+  public void setProcessCanTerminate(boolean processCanTerminate) {
+    myProcessCanTerminate = processCanTerminate;
+  }
+
+  protected void clearAllBreakpoints() {
+
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        XDebuggerTestUtil.removeAllBreakpoints(getProject());
+      }
+    });
+  }
+
+  /**
+   * Toggles breakpoint
+   *
+   * @param file getScriptPath() or path to script
+   * @param line starting with 0
+   */
+  protected void toggleBreakpoint(final String file, final int line) {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        doToggleBreakpoint(file, line);
+      }
+    });
+
+    addOrRemoveBreakpoint(file, line);
+  }
+
+  private void addOrRemoveBreakpoint(String file, int line) {
+    if (myBreakpoints.contains(Pair.create(file, line))) {
+      myBreakpoints.remove(Pair.create(file, line));
+    }
+    else {
+      myBreakpoints.add(Pair.create(file, line));
+    }
+  }
+
+  protected void toggleBreakpointInEgg(final String file, final String innerPath, final int line) {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        VirtualFile f = VirtualFileUtil.findFile(file);
+        Assert.assertNotNull(f);
+        final VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(f);
+        Assert.assertNotNull(jarRoot);
+        VirtualFile innerFile = jarRoot.findFileByRelativePath(innerPath);
+        Assert.assertNotNull(innerFile);
+        XDebuggerTestUtil.toggleBreakpoint(getProject(), innerFile, line);
+      }
+    });
+
+    addOrRemoveBreakpoint(file, line);
+  }
+
+  public boolean canPutBreakpointAt(Project project, String file, int line) {
+    VirtualFile vFile = VirtualFileUtil.findFile(file);
+    Assert.assertNotNull(vFile);
+    return XDebuggerUtil.getInstance().canPutBreakpointAt(project, vFile, line);
+  }
+
+  private void doToggleBreakpoint(String file, int line) {
+    Assert.assertTrue(canPutBreakpointAt(getProject(), file, line));
+    XDebuggerTestUtil.toggleBreakpoint(getProject(), VirtualFileUtil.findFile(file), line);
+  }
+
+  protected Variable eval(String name) throws InterruptedException {
+    Assert.assertTrue("Eval works only while suspended", mySession.isSuspended());
+    XValue var = XDebuggerTestUtil.evaluate(mySession, name).first;
+    Assert.assertNotNull("There is no variable named " + name, var);
+    return new Variable(var);
+  }
+
+  protected void setVal(String name, String value) throws InterruptedException, PyDebuggerException {
+    XValue var = XDebuggerTestUtil.evaluate(mySession, name).first;
+    myDebugProcess.changeVariable((PyDebugValue)var, value);
+  }
+
+  public void waitForOutput(String ... string) throws InterruptedException {
+    long started = System.currentTimeMillis();
+
+    while (!containsOneOf(output(), string)) {
+      if (System.currentTimeMillis() - started > myTimeout) {
+        Assert.fail("None of '" + StringUtil.join(string, ", ") + "'" + " is not present in output.\n" + output());
+      }
+      Thread.sleep(2000);
+    }
+  }
+
+  protected boolean containsOneOf(String output, String[] strings) {
+    for (String s: strings) {
+      if (output.contains(s)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  public void setShouldPrintOutput(boolean shouldPrintOutput) {
+    this.shouldPrintOutput = shouldPrintOutput;
+  }
+
+  @Override
+  public void setUp(final String testName) throws Exception {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          if (myFixture == null) {
+            PyBaseDebuggerTask.super.setUp(testName);
+          }
+        }
+        catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      public void run() {
+        try {
+          if (mySession != null) {
+            finishSession();
+          }
+          PyBaseDebuggerTask.super.tearDown();
+        }
+        catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+  }
+
+  protected void finishSession() throws InterruptedException {
+    disposeDebugProcess();
+
+    if (mySession != null) {
+      new WriteAction() {
+        protected void run(Result result) throws Throwable {
+          mySession.stop();
+        }
+      }.execute();
+
+      waitFor(mySession.getDebugProcess().getProcessHandler()); //wait for process termination after session.stop() which is async
+
+      XDebuggerTestUtil.disposeDebugSession(mySession);
+      mySession = null;
+      myDebugProcess = null;
+      myPausedSemaphore = null;
+    }
+  }
+
+  protected abstract void disposeDebugProcess() throws InterruptedException;
+
+  protected void doTest(@Nullable OutputPrinter myOutputPrinter) throws InterruptedException {
+    try {
+      testing();
+      after();
+    }
+    catch (Throwable e) {
+      throw new RuntimeException(output(), e);
+    }
+    finally {
+      clearAllBreakpoints();
+
+      setProcessCanTerminate(true);
+
+      if (myOutputPrinter != null) {
+        myOutputPrinter.stop();
+      }
+
+      finishSession();
+    }
+  }
+
+  protected static class Variable {
+    private final XTestValueNode myValueNode;
+
+    public Variable(XValue value) throws InterruptedException {
+      myValueNode = XDebuggerTestUtil.computePresentation(value);
+    }
+
+    public Variable hasValue(String value) {
+      Assert.assertEquals(value, myValueNode.myValue);
+      return this;
+    }
+
+    public Variable hasType(String type) {
+      Assert.assertEquals(type, myValueNode.myType);
+      return this;
+    }
+
+    public Variable hasName(String name) {
+      Assert.assertEquals(name, myValueNode.myName);
+      return this;
+    }
+  }
+
+  public class OutputPrinter {
+    private Thread myThread;
+
+    public void start() {
+      myThread = new Thread(new Runnable() {
+        @Override
+        public void run() {
+          doJob();
+        }
+      });
+      myThread.setDaemon(true);
+      myThread.start();
+    }
+
+    private void doJob() {
+      int len = 0;
+      try {
+        while (true) {
+          String s = output();
+          if (s.length() > len) {
+            System.out.print(s.substring(len));
+          }
+          len = s.length();
+
+          Thread.sleep(500);
+        }
+      }
+      catch (Exception e) {
+      }
+    }
+
+    public void stop() {
+      myThread.interrupt();
+    }
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java b/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java
new file mode 100644 (file)
index 0000000..d2923f3
--- /dev/null
@@ -0,0 +1,211 @@
+package com.jetbrains.env.community.python.debug;
+
+import com.intellij.execution.*;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.executors.DefaultDebugExecutor;
+import com.intellij.execution.process.KillableColoredProcessHandler;
+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.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.xdebugger.*;
+import com.jetbrains.python.debugger.PyDebugProcess;
+import com.jetbrains.python.debugger.PyDebugRunner;
+import com.jetbrains.python.run.PythonCommandLineState;
+import com.jetbrains.python.run.PythonConfigurationType;
+import com.jetbrains.python.run.PythonRunConfiguration;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.concurrent.Semaphore;
+
+/**
+ * @author traff
+ */
+public class PyDebuggerTask extends PyBaseDebuggerTask {
+
+  private boolean myMultiprocessDebug = false;
+  private PythonRunConfiguration myRunConfiguration;
+
+  public PyDebuggerTask() {
+    init();
+  }
+
+  public PyDebuggerTask(String workingFolder, String scriptName, String scriptParameters) {
+    setWorkingFolder(getTestDataPath() + workingFolder);
+    setScriptName(scriptName);
+    setScriptParameters(scriptParameters);
+    init();
+  }
+
+  public PyDebuggerTask(String workingFolder, String scriptName) {
+    this(workingFolder, scriptName, null);
+  }
+
+  protected void init() {
+
+  }
+
+  public void runTestOn(String sdkHome) throws Exception {
+    final Project project = getProject();
+
+    final ConfigurationFactory factory = PythonConfigurationType.getInstance().getConfigurationFactories()[0];
+
+
+    final RunnerAndConfigurationSettings settings =
+      RunManager.getInstance(project).createRunConfiguration("test", factory);
+
+    myRunConfiguration = (PythonRunConfiguration)settings.getConfiguration();
+
+    myRunConfiguration.setSdkHome(sdkHome);
+    myRunConfiguration.setScriptName(getScriptPath());
+    myRunConfiguration.setWorkingDirectory(getWorkingFolder());
+    myRunConfiguration.setScriptParameters(getScriptParameters());
+
+    new WriteAction() {
+      @Override
+      protected void run(Result result) throws Throwable {
+        RunManagerEx.getInstanceEx(project).addConfiguration(settings, false);
+        RunManagerEx.getInstanceEx(project).setSelectedConfiguration(settings);
+        Assert.assertSame(settings, RunManagerEx.getInstanceEx(project).getSelectedConfiguration());
+      }
+    }.execute();
+
+    final PyDebugRunner runner = (PyDebugRunner)ProgramRunnerUtil.getRunner(DefaultDebugExecutor.EXECUTOR_ID, settings);
+    Assert.assertTrue(runner.canRun(DefaultDebugExecutor.EXECUTOR_ID, myRunConfiguration));
+
+    final Executor executor = DefaultDebugExecutor.getDebugExecutorInstance();
+    final ExecutionEnvironment env = new ExecutionEnvironment(executor, runner, settings, project);
+
+    final PythonCommandLineState pyState = (PythonCommandLineState)myRunConfiguration.getState(executor, env);
+
+    assert pyState != null;
+    pyState.setMultiprocessDebug(isMultiprocessDebug());
+
+    final ServerSocket serverSocket;
+    try {
+      //noinspection SocketOpenedButNotSafelyClosed
+      serverSocket = new ServerSocket(0);
+    }
+    catch (IOException e) {
+      throw new ExecutionException("Failed to find free socket port", e);
+    }
+
+
+    final int serverLocalPort = serverSocket.getLocalPort();
+    final RunProfile profile = env.getRunProfile();
+
+    before();
+
+    setProcessCanTerminate(false);
+
+    myTerminateSemaphore = new Semaphore(0);
+    
+    new WriteAction<ExecutionResult>() {
+      @Override
+      protected void run(@NotNull Result<ExecutionResult> result) throws Throwable {
+        final ExecutionResult res =
+          pyState.execute(executor, PyDebugRunner.createCommandLinePatchers(myFixture.getProject(), pyState, profile, serverLocalPort));
+
+        mySession = XDebuggerManager.getInstance(getProject()).
+          startSession(runner, env, env.getContentToReuse(), new XDebugProcessStarter() {
+            @NotNull
+            public XDebugProcess start(@NotNull final XDebugSession session) {
+              myDebugProcess =
+                new PyDebugProcess(session, serverSocket, res.getExecutionConsole(), res.getProcessHandler(), isMultiprocessDebug());
+
+              myDebugProcess.getProcessHandler().addProcessListener(new ProcessAdapter() {
+
+                @Override
+                public void onTextAvailable(ProcessEvent event, Key outputType) {
+                }
+
+                @Override
+                public void processTerminated(ProcessEvent event) {
+                  myTerminateSemaphore.release();
+                  if (event.getExitCode() != 0 && !myProcessCanTerminate) {
+                    Assert.fail("Process terminated unexpectedly\n" + output());
+                  }
+                }
+              });
+
+
+              myDebugProcess.getProcessHandler().startNotify();
+
+              return myDebugProcess;
+            }
+          });
+        result.setResult(res);
+      }
+    }.execute().getResultObject();
+
+    OutputPrinter myOutputPrinter = null;
+    if (shouldPrintOutput) {
+      myOutputPrinter = new OutputPrinter();
+      myOutputPrinter.start();
+    }
+
+
+    myPausedSemaphore = new Semaphore(0);
+    
+
+    mySession.addSessionListener(new XDebugSessionAdapter() {
+      @Override
+      public void sessionPaused() {
+        if (myPausedSemaphore != null) {
+          myPausedSemaphore.release();
+        }
+      }
+    });
+
+    doTest(myOutputPrinter);
+  }
+
+  public PythonRunConfiguration getRunConfiguration() {
+    return myRunConfiguration;
+  }
+
+  private boolean isMultiprocessDebug() {
+    return myMultiprocessDebug;
+  }
+
+  public void setMultiprocessDebug(boolean multiprocessDebug) {
+    myMultiprocessDebug = multiprocessDebug;
+  }
+
+  @Override
+  protected void disposeDebugProcess() throws InterruptedException {
+    if (myDebugProcess != null) {
+      ProcessHandler processHandler = myDebugProcess.getProcessHandler();
+
+      myDebugProcess.stop();
+
+      waitFor(processHandler);
+
+      if (!processHandler.isProcessTerminated()) {
+        killDebugProcess();
+        if (!waitFor(processHandler)) {
+          new Throwable("Cannot stop debugger process").printStackTrace();
+        }
+      }
+    }
+  }
+
+  private void killDebugProcess() {
+    if (myDebugProcess.getProcessHandler() instanceof KillableColoredProcessHandler) {
+      KillableColoredProcessHandler h = (KillableColoredProcessHandler)myDebugProcess.getProcessHandler();
+
+      h.killProcess();
+    }
+    else {
+      myDebugProcess.getProcessHandler().destroyProcess();
+    }
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/dotNet/PyIronPythonTest.java b/python/testSrc/com/jetbrains/env/python/dotNet/PyIronPythonTest.java
new file mode 100644 (file)
index 0000000..70f9b24
--- /dev/null
@@ -0,0 +1,145 @@
+package com.jetbrains.env.community.python.dotNet;
+
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.python.psi.PyFile;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+/**
+ * IronPython.NET specific tests
+ *
+ * @author Ilya.Kazakevich
+ */
+public class PyIronPythonTest extends PyEnvTestCase {
+
+  /**
+   * IronPython tag
+   */
+  static final String IRON_TAG = "iron";
+
+  public PyIronPythonTest() {
+    super(IRON_TAG);
+  }
+
+  /**
+   * Tests skeleton generation
+   */
+  public void testSkeletons() throws Exception {
+    runPythonTest(new SkeletonTestTask(
+      "dotNet/expected.skeleton.java.py",
+      "com.just.like.java",
+      "testSkeleton.py",
+      null
+    ));
+  }
+
+  /**
+   * Tests skeleton generation with "from" statements
+   */
+  public void testClassFromModule() throws Exception {
+    runPythonTest(new SkeletonTestTask(
+      "dotNet/expected.skeleton.java.py",
+      "com.just.like.java",
+      "import_class_from_module.py",
+      null
+    ));
+  }
+
+  /**
+   * Tests skeleton generation when imported as alias
+   */
+  public void testClassFromModuleAlias() throws Exception {
+    runPythonTest(new SkeletonTestTask(
+      "dotNet/expected.skeleton.java.py",
+      "com.just.like.java",
+      "import_class_from_module_alias.py",
+      null
+    ));
+  }
+
+  /**
+   * Tests skeleton generation when module is imported
+   */
+  public void testModuleFromPackage() throws Exception {
+    runPythonTest(new SkeletonTestTask(
+      "dotNet/expected.skeleton.java.py",
+      "com.just.like.java",
+      "import_module_from_package.py",
+      null
+    ));
+  }
+
+  /**
+   * Tests skeleton generation when several classes are imported
+   */
+  public void testSeveralClasses() throws Exception {
+    runPythonTest(new SkeletonTestTask(
+      "dotNet/expected.skeleton.java.py",
+      "com.just.like.java",
+      "import_several_classes_from_module.py",
+      "com.just.like.java.LikeJavaClass"
+    ));
+  }
+
+  /**
+   * Tests skeletons for built-in classes. We can't compare files (CLR class may be changed from version to version),
+   * but we are sure there should be class System.Web.AspNetHostingPermissionLevel which is part of public API
+   */
+  public void testImportBuiltInSystem() throws Exception {
+    final SkeletonTestTask task = new SkeletonTestTask(
+      null,
+      "System.Web",
+      "import_system.py",
+      null
+    );
+    runPythonTest(task);
+    final PyFile skeleton = task.getGeneratedSkeleton();
+    new ReadAction() {
+      @Override
+      protected void run(@NotNull Result result) throws Throwable {
+        Assert.assertNotNull("System.Web does not contain class AspNetHostingPermissionLevel. Error generating stub? It has classes  " +
+                             skeleton.getTopLevelClasses(),
+                             skeleton.findTopLevelClass("AspNetHostingPermissionLevel"));
+      }
+    }.execute();
+
+  }
+
+  /**
+   * Test importing of inner classes
+   */
+  public void testImportInnerClass() throws Exception {
+    runPythonTest(new SkeletonTestTask(
+      "dotNet/expected.skeleton.Deep.py",
+      "SingleNameSpace.Some.Deep",
+      "inner_class.py",
+      null
+    ));
+  }
+
+  /**
+   * Test importing of the whole namespace
+   */
+  public void testWholeNameSpace() throws Exception {
+    runPythonTest(new SkeletonTestTask(
+      "dotNet/expected.skeleton.SingleNameSpace.py",
+      "SingleNameSpace",
+      "whole_namespace.py",
+      null
+    ));
+  }
+
+  /**
+   * Test importing of single class
+   */
+  public void testSingleClass() throws Exception {
+    runPythonTest(new SkeletonTestTask(
+      "dotNet/expected.skeleton.SingleNameSpace.py",
+      "SingleNameSpace",
+      "single_class.py",
+      null
+    ));
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/dotNet/SkeletonTestTask.java b/python/testSrc/com/jetbrains/env/python/dotNet/SkeletonTestTask.java
new file mode 100644 (file)
index 0000000..f2b788b
--- /dev/null
@@ -0,0 +1,142 @@
+package com.jetbrains.env.community.python.dotNet;
+
+import com.google.common.collect.Sets;
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.codeInspection.LocalQuickFix;
+import com.intellij.codeInspection.ex.QuickFixWrapper;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.progress.util.AbstractProgressIndicatorBase;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.util.ui.UIUtil;
+import com.jetbrains.env.community.PyExecutionFixtureTestTask;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.inspections.quickfix.GenerateBinaryStubsFix;
+import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection;
+import com.jetbrains.python.psi.PyFile;
+import com.jetbrains.python.sdk.InvalidSdkException;
+import com.jetbrains.python.sdk.PythonSdkType;
+import com.jetbrains.python.sdkTools.SdkCreationType;
+import org.hamcrest.Matchers;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Assert;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Task for test that checks skeleton generation
+ *
+ * @author Ilya.Kazakevich
+ */
+class SkeletonTestTask extends PyExecutionFixtureTestTask {
+
+  /**
+   * Tags for this task to run
+   */
+  private static final Set<String> IRON_TAGS = Sets.newHashSet(PyIronPythonTest.IRON_TAG);
+  /**
+   * Number of seconds we wait for skeleton generation external process (should be enough)
+   */
+  private static final int SECONDS_TO_WAIT_FOR_SKELETON_GENERATION = 20;
+
+  @Nullable
+  private final String myExpectedSkeletonFile;
+  @NotNull
+  private final String myModuleNameToBeGenerated;
+  @NotNull
+  private final String mySourceFileToRunGenerationOn;
+  @NotNull
+  private final String myUseQuickFixWithThisModuleOnly;
+  private PyFile myGeneratedSkeleton;
+
+  /**
+   * @param expectedSkeletonFile          if you want test to compare generated result with some file, provide its name.
+   *                                      Pass null if you do not want to compare result with anything (you may do it yourself with {@link #getGeneratedSkeleton()})
+   * @param moduleNameToBeGenerated       name of module you think we should generate in dotted notation (like "System.Web" or "com.myModule").
+   *                                      System will wait for skeleton file for this module to be generated
+   * @param sourceFileToRunGenerationOn   Source file where we should run "generate stubs" on. Be sure to place "caret" on appropriate place!
+   * @param useQuickFixWithThisModuleOnly If there are several quick fixes in code, you may run fix only on this module.
+   *                                      Pass null if you are sure there would be only one quickfix
+   */
+  SkeletonTestTask(@Nullable final String expectedSkeletonFile,
+                   @NotNull final String moduleNameToBeGenerated,
+                   @NotNull final String sourceFileToRunGenerationOn,
+                   @Nullable final String useQuickFixWithThisModuleOnly) {
+    myExpectedSkeletonFile = expectedSkeletonFile;
+    myModuleNameToBeGenerated = moduleNameToBeGenerated.replace('.', '/');
+    mySourceFileToRunGenerationOn = sourceFileToRunGenerationOn;
+    myUseQuickFixWithThisModuleOnly = useQuickFixWithThisModuleOnly != null ? useQuickFixWithThisModuleOnly : "";
+  }
+
+
+  @Override
+  public void runTestOn(@NotNull final String sdkHome) throws IOException, InvalidSdkException {
+    final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.SDK_PACKAGES_ONLY);
+    final File skeletonsPath = new File(PythonSdkType.getSkeletonsPath(PathManager.getSystemPath(), sdk.getHomePath()));
+    File skeletonFileOrDirectory = new File(skeletonsPath, myModuleNameToBeGenerated); // File with module skeleton
+
+    // Module may be stored in "moduleName.py" or "moduleName/__init__.py"
+    if (skeletonFileOrDirectory.isDirectory()) {
+      skeletonFileOrDirectory = new File(skeletonFileOrDirectory, PyNames.INIT_DOT_PY);
+    }
+    else {
+      skeletonFileOrDirectory = new File(skeletonFileOrDirectory.getAbsolutePath() + PyNames.DOT_PY);
+    }
+
+    final File skeletonFile = skeletonFileOrDirectory;
+
+    if (skeletonFile.exists()) { // To make sure we do not reuse it
+      assert skeletonFile.delete() : "Failed to delete file " + skeletonFile;
+    }
+
+    myFixture.copyFileToProject("dotNet/" + mySourceFileToRunGenerationOn, mySourceFileToRunGenerationOn); // File that uses CLR library
+    myFixture.copyFileToProject("dotNet/PythonLibs.dll", "PythonLibs.dll"); // Library itself
+    myFixture.copyFileToProject("dotNet/SingleNameSpace.dll", "SingleNameSpace.dll"); // Another library
+    myFixture.configureByFile(mySourceFileToRunGenerationOn);
+    myFixture.enableInspections(PyUnresolvedReferencesInspection.class); // This inspection should suggest us to generate stubs
+
+
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        PsiDocumentManager.getInstance(myFixture.getProject()).commitAllDocuments();
+        final String intentionName = PyBundle.message("sdk.gen.stubs.for.binary.modules", myUseQuickFixWithThisModuleOnly);
+        final IntentionAction intention = myFixture.findSingleIntention(intentionName);
+        Assert.assertNotNull("No intention found to generate skeletons!", intention);
+        Assert.assertThat("Intention should be quick fix to run", intention, Matchers.instanceOf(QuickFixWrapper.class));
+        final LocalQuickFix quickFix = ((QuickFixWrapper)intention).getFix();
+        Assert.assertThat("Quick fix should be 'generate binary skeletons' fix to run", quickFix,
+                          Matchers.instanceOf(GenerateBinaryStubsFix.class));
+        final Task fixTask = ((GenerateBinaryStubsFix)quickFix).getFixTask(myFixture.getFile());
+        fixTask.run(new AbstractProgressIndicatorBase());
+      }
+    });
+
+    FileUtil.copy(skeletonFile, new File(myFixture.getTempDirPath(), skeletonFile.getName()));
+    if (myExpectedSkeletonFile != null) {
+      myFixture.checkResultByFile(skeletonFile.getName(), myExpectedSkeletonFile, false);
+    }
+    myGeneratedSkeleton = (PyFile)myFixture.configureByFile(skeletonFile.getName());
+  }
+
+
+  @Override
+  public Set<String> getTags() {
+    return Collections.unmodifiableSet(IRON_TAGS);
+  }
+
+  /**
+   * @return File for generated skeleton. Call it after {@link #runTestOn(String)} only!
+   */
+  @NotNull
+  PyFile getGeneratedSkeleton() {
+    return myGeneratedSkeleton;
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/dotNet/package-info.java b/python/testSrc/com/jetbrains/env/python/dotNet/package-info.java
new file mode 100644 (file)
index 0000000..9facb61
--- /dev/null
@@ -0,0 +1,5 @@
+/**
+ * IronPython tests. Install IronPython, .NET and add "iron" to "tags.txt" file. Check for more info: http://confluence.jetbrains.com/display/PYINT/Env+Tests
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.env.community.python.dotNet;
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonDocTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonDocTestingTest.java
new file mode 100644 (file)
index 0000000..178673f
--- /dev/null
@@ -0,0 +1,66 @@
+package com.jetbrains.env.community.python.testing;
+
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.ut.PyDocTestTask;
+
+/**
+ * User : catherine
+ */
+public class PythonDocTestingTest extends PyEnvTestCase{
+  public void testUTRunner() {
+    runPythonTest(new PyDocTestTask("/testRunner/env/doc", "test1.py") {
+
+      @Override
+      public void after() {
+        assertEquals(3, allTestsCount());
+        assertEquals(3, passedTestsCount());
+        allTestsPassed();
+      }
+    });
+  }
+
+  public void testClass() {
+    runPythonTest(new PyDocTestTask("/testRunner/env/doc", "test1.py::FirstGoodTest") {
+
+      @Override
+      public void after() {
+        assertEquals(1, allTestsCount());
+        assertEquals(1, passedTestsCount());
+      }
+    });
+  }
+
+  public void testMethod() {
+    runPythonTest(new PyDocTestTask("/testRunner/env/doc", "test1.py::SecondGoodTest::test_passes") {
+
+      @Override
+      public void after() {
+        assertEquals(1, allTestsCount());
+        assertEquals(1, passedTestsCount());
+      }
+    });
+  }
+
+  public void testFunction() {
+    runPythonTest(new PyDocTestTask("/testRunner/env/doc", "test1.py::factorial") {
+
+      @Override
+      public void after() {
+        assertEquals(1, allTestsCount());
+        assertEquals(1, passedTestsCount());
+      }
+    });
+  }
+
+  public void testUTRunner2() {
+    runPythonTest(new PyDocTestTask("/testRunner/env/doc", "test2.py") {
+
+      @Override
+      public void after() {
+        assertEquals(3, allTestsCount());
+        assertEquals(1, passedTestsCount());
+        assertEquals(2, failedTestsCount());
+      }
+    });
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java
new file mode 100644 (file)
index 0000000..7bc3d43
--- /dev/null
@@ -0,0 +1,33 @@
+package com.jetbrains.env.community.python.testing;
+
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.ut.PyNoseTestTask;
+
+/**
+ * User : catherine
+ */
+public class PythonNoseTestingTest extends PyEnvTestCase{
+  public void testNoseRunner() {
+    runPythonTest(new PyNoseTestTask("/testRunner/env/nose", "test1.py") {
+
+      @Override
+      public void after() {
+        assertEquals(3, allTestsCount());
+        assertEquals(3, passedTestsCount());
+        allTestsPassed();
+      }
+    });
+  }
+
+  public void testNoseRunner2() {
+    runPythonTest(new PyNoseTestTask("/testRunner/env/nose", "test2.py") {
+
+      @Override
+      public void after() {
+        assertEquals(8, allTestsCount());
+        assertEquals(5, passedTestsCount());
+        assertEquals(3, failedTestsCount());
+      }
+    });
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java
new file mode 100644 (file)
index 0000000..94463b8
--- /dev/null
@@ -0,0 +1,33 @@
+package com.jetbrains.env.community.python.testing;
+
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.ut.PyTestTestTask;
+
+/**
+ * User : catherine
+ */
+public class PythonPyTestingTest extends PyEnvTestCase{
+  public void testPytestRunner() {
+    runPythonTest(new PyTestTestTask("/testRunner/env/pytest", "test1.py") {
+
+      @Override
+      public void after() {
+        assertEquals(3, allTestsCount());
+        assertEquals(3, passedTestsCount());
+        allTestsPassed();
+      }
+    });
+  }
+
+  public void testPytestRunner2() {
+    runPythonTest(new PyTestTestTask("/testRunner/env/pytest", "test2.py") {
+
+      @Override
+      public void after() {
+        assertEquals(8, allTestsCount());
+        assertEquals(5, passedTestsCount());
+        assertEquals(3, failedTestsCount());
+      }
+    });
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java
new file mode 100644 (file)
index 0000000..892db6c
--- /dev/null
@@ -0,0 +1,78 @@
+package com.jetbrains.env.community.python.testing;
+
+import com.jetbrains.env.community.PyEnvTestCase;
+import com.jetbrains.env.community.ut.PyUnitTestTask;
+import junit.framework.Assert;
+
+/**
+ * @author traff
+ */
+public class PythonUnitTestingTest extends PyEnvTestCase{
+  public void testUTRunner() {
+    runPythonTest(new PyUnitTestTask("/testRunner/env/unit", "test1.py") {
+
+      @Override
+      public void after() {
+        Assert.assertEquals(2, allTestsCount());
+        Assert.assertEquals(2, passedTestsCount());
+        allTestsPassed();
+      }
+    });
+  }
+
+  public void testUTRunner2() {
+    runPythonTest(new PyUnitTestTask("/testRunner/env/unit", "test2.py") {
+
+      @Override
+      public void after() {
+        assertEquals(3, allTestsCount());
+        assertEquals(1, passedTestsCount());
+        assertEquals(2, failedTestsCount());
+      }
+    });
+  }
+
+  public void testClass() {
+    runPythonTest(new PyUnitTestTask("/testRunner/env/unit", "test_file.py::GoodTest") {
+
+      @Override
+      public void after() {
+        assertEquals(1, allTestsCount());
+        assertEquals(1, passedTestsCount());
+      }
+    });
+  }
+
+  public void testMethod() {
+    runPythonTest(new PyUnitTestTask("/testRunner/env/unit", "test_file.py::GoodTest::test_passes") {
+
+      @Override
+      public void after() {
+        assertEquals(1, allTestsCount());
+        assertEquals(1, passedTestsCount());
+      }
+    });
+  }
+
+  public void testFolder() {
+    runPythonTest(new PyUnitTestTask("/testRunner/env/unit", "test_folder/") {
+
+      @Override
+      public void after() {
+        assertEquals(5, allTestsCount());
+        assertEquals(3, passedTestsCount());
+      }
+    });
+  }
+
+  public void testDependent() {
+    runPythonTest(new PyUnitTestTask("/testRunner/env/unit", "dependentTests/test_my_class.py") {
+
+      @Override
+      public void after() {
+        assertEquals(1, allTestsCount());
+        assertEquals(1, passedTestsCount());
+      }
+    });
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/ut/PyDocTestTask.java b/python/testSrc/com/jetbrains/env/ut/PyDocTestTask.java
new file mode 100644 (file)
index 0000000..bfc62fa
--- /dev/null
@@ -0,0 +1,24 @@
+package com.jetbrains.env.community.ut;
+
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.python.testing.PythonTestConfigurationType;
+
+/**
+ * User : catherine
+ */
+public abstract class PyDocTestTask extends PyUnitTestTask {
+  public PyDocTestTask(String workingFolder, String scriptName, String scriptParameters) {
+    super(workingFolder, scriptName, scriptParameters);
+  }
+
+  public PyDocTestTask(String workingFolder, String scriptName) {
+    this(workingFolder, scriptName, null);
+  }
+
+  public void runTestOn(String sdkHome) throws Exception {
+    final Project project = getProject();
+    final ConfigurationFactory factory = PythonTestConfigurationType.getInstance().PY_DOCTEST_FACTORY;
+    runConfiguration(factory, sdkHome, project);
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/ut/PyNoseTestTask.java b/python/testSrc/com/jetbrains/env/ut/PyNoseTestTask.java
new file mode 100644 (file)
index 0000000..7d26d96
--- /dev/null
@@ -0,0 +1,32 @@
+package com.jetbrains.env.community.ut;
+
+import com.google.common.collect.ImmutableSet;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.python.testing.PythonTestConfigurationType;
+
+import java.util.Set;
+
+/**
+ * User : catherine
+ */
+public abstract class PyNoseTestTask extends PyUnitTestTask {
+  public PyNoseTestTask(String workingFolder, String scriptName, String scriptParameters) {
+    super(workingFolder, scriptName, scriptParameters);
+  }
+
+  public PyNoseTestTask(String workingFolder, String scriptName) {
+    this(workingFolder, scriptName, null);
+  }
+
+  public void runTestOn(String sdkHome) throws Exception {
+    final Project project = getProject();
+    final ConfigurationFactory factory = PythonTestConfigurationType.getInstance().PY_NOSETEST_FACTORY;
+    runConfiguration(factory, sdkHome, project);
+  }
+
+  @Override
+  public Set<String> getTags() {
+    return ImmutableSet.of("nose");
+  }
+}
diff --git a/python/testSrc/com/jetbrains/env/ut/PyTestTestTask.java b/python/testSrc/com/jetbrains/env/ut/PyTestTestTask.java
new file mode 100644 (file)
index 0000000..e80d40a
--- /dev/null
@@ -0,0 +1,40 @@
+package com.jetbrains.env.community.ut;
+
+import com.google.common.collect.ImmutableSet;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.python.testing.AbstractPythonTestRunConfiguration;
+import com.jetbrains.python.testing.PythonTestConfigurationType;
+import com.jetbrains.python.testing.pytest.PyTestRunConfiguration;
+
+import java.util.Set;
+
+/**
+ * User : catherine
+ */
+public abstract class PyTestTestTask extends PyUnitTestTask {
+  public PyTestTestTask(String workingFolder, String scriptName, String scriptParameters) {
+    super(workingFolder, scriptName, scriptParameters);
+  }
+
+  public PyTestTestTask(String workingFolder, String scriptName) {
+    this(workingFolder, scriptName, null);
+  }
+
+  public void runTestOn(String sdkHome) throws Exception {
+    final Project project = getProject();
+    final ConfigurationFactory factory = PythonTestConfigurationType.getInstance().PY_PYTEST_FACTORY;
+    runConfiguration(factory, sdkHome, project);
+  }
+
+  @Override
+  public Set<String> getTags() {
+    return ImmutableSet.of("pytest");
+  }
+
+  protected void configure(AbstractPythonTestRunConfiguration config) {
+    if (config instanceof PyTestRunConfiguration)
+      ((PyTestRunConfiguration)config).setTestToRun(getScriptPath());
+  }
+
+}
diff --git a/python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java b/python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java
new file mode 100644 (file)
index 0000000..ce441dd
--- /dev/null
@@ -0,0 +1,240 @@
+package com.jetbrains.env.community.ut;
+
+import com.google.common.collect.Lists;
+import com.intellij.execution.*;
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.executors.DefaultRunExecutor;
+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.execution.runners.ProgramRunner;
+import com.intellij.execution.testframework.Filter;
+import com.intellij.execution.testframework.sm.runner.SMTestProxy;
+import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
+import com.intellij.execution.testframework.sm.runner.ui.TestResultsViewer;
+import com.intellij.execution.ui.RunContentDescriptor;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Key;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.xdebugger.XDebuggerTestUtil;
+import com.jetbrains.env.community.PyExecutionFixtureTestTask;
+import com.jetbrains.python.sdk.PythonEnvUtil;
+import com.jetbrains.python.sdk.flavors.JythonSdkFlavor;
+import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
+import com.jetbrains.python.testing.AbstractPythonTestRunConfiguration;
+import com.jetbrains.python.testing.PythonTestConfigurationType;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+/**
+ * @author traff
+ */
+public abstract class PyUnitTestTask extends PyExecutionFixtureTestTask {
+
+  protected ProcessHandler myProcessHandler;
+  private boolean shouldPrintOutput = false;
+  private SMTestProxy.SMRootTestProxy myTestProxy;
+  private boolean mySetUp = false;
+  private SMTRunnerConsoleView myConsoleView;
+  private RunContentDescriptor myDescriptor;
+  private StringBuilder myOutput;
+
+  public PyUnitTestTask() {
+  }
+
+  public PyUnitTestTask(String workingFolder, String scriptName, String scriptParameters) {
+    setWorkingFolder(getTestDataPath() + workingFolder);
+    setScriptName(scriptName);
+    setScriptParameters(scriptParameters);
+  }
+
+  public PyUnitTestTask(String workingFolder, String scriptName) {
+    this(workingFolder, scriptName, null);
+  }
+
+  @Override
+  public void setUp(final String testName) throws Exception {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          if (myFixture == null) {
+            PyUnitTestTask.super.setUp(testName);
+            mySetUp = true;
+          }
+        }
+        catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+  }
+
+  @NotNull
+  protected String output() {
+    return myOutput.toString();
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      public void run() {
+        try {
+          if (mySetUp) {
+            if (myConsoleView != null) {
+              Disposer.dispose(myConsoleView);
+              myConsoleView = null;
+            }
+            if (myDescriptor != null) {
+              Disposer.dispose(myDescriptor);
+              myDescriptor = null;
+            }
+
+
+            PyUnitTestTask.super.tearDown();
+
+            mySetUp = false;
+          }
+        }
+        catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    }
+
+    );
+  }
+
+  public void runTestOn(String sdkHome) throws Exception {
+    final Project project = getProject();
+    final ConfigurationFactory factory = PythonTestConfigurationType.getInstance().PY_UNITTEST_FACTORY;
+    runConfiguration(factory, sdkHome, project);
+  }
+
+  protected void runConfiguration(ConfigurationFactory factory, String sdkHome, final Project project) throws Exception {
+    final RunnerAndConfigurationSettings settings =
+      RunManager.getInstance(project).createRunConfiguration("test", factory);
+
+    AbstractPythonTestRunConfiguration config = (AbstractPythonTestRunConfiguration)settings.getConfiguration();
+
+
+    config.setSdkHome(sdkHome);
+    config.setScriptName(getScriptPath());
+    config.setWorkingDirectory(getWorkingFolder());
+
+    PythonSdkFlavor sdk = PythonSdkFlavor.getFlavor(sdkHome);
+
+
+    if (sdk instanceof JythonSdkFlavor) {
+      config.setInterpreterOptions(JythonSdkFlavor.getPythonPathCmdLineArgument(Lists.<String>newArrayList(getWorkingFolder())));
+    }
+    else {
+      PythonEnvUtil.addToPythonPath(config.getEnvs(), getWorkingFolder());
+    }
+
+
+    configure(config);
+
+    new WriteAction() {
+      @Override
+      protected void run(Result result) throws Throwable {
+        RunManagerEx.getInstanceEx(project).addConfiguration(settings, false);
+        RunManagerEx.getInstanceEx(project).setSelectedConfiguration(settings);
+        Assert.assertSame(settings, RunManagerEx.getInstanceEx(project).getSelectedConfiguration());
+      }
+    }.execute();
+
+    final ProgramRunner runner = ProgramRunnerUtil.getRunner(DefaultRunExecutor.EXECUTOR_ID, settings);
+
+    Assert.assertTrue(runner.canRun(DefaultRunExecutor.EXECUTOR_ID, config));
+
+    final Executor executor = DefaultRunExecutor.getRunExecutorInstance();
+    final ExecutionEnvironment env = new ExecutionEnvironment(executor, runner, settings, project);
+
+    before();
+
+    final com.intellij.util.concurrency.Semaphore s = new com.intellij.util.concurrency.Semaphore();
+    s.down();
+
+    myOutput = new StringBuilder();
+
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      public void run() {
+        try {
+          runner.execute(env, new ProgramRunner.Callback() {
+            @Override
+            public void processStarted(RunContentDescriptor descriptor) {
+              myDescriptor = descriptor;
+              myProcessHandler = myDescriptor.getProcessHandler();
+              myProcessHandler.addProcessListener(new ProcessAdapter() {
+                @Override
+                public void onTextAvailable(ProcessEvent event, Key outputType) {
+                  myOutput.append(event.getText());
+                }
+              });
+              myConsoleView = (com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView)descriptor.getExecutionConsole();
+              myTestProxy = myConsoleView.getResultsViewer().getTestsRootNode();
+              myConsoleView.getResultsViewer().addEventsListener(new TestResultsViewer.SMEventsAdapter() {
+                @Override
+                public void onTestingFinished(TestResultsViewer sender) {
+                  s.up();
+                }
+              });
+            }
+          });
+        }
+        catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+
+    Assert.assertTrue(s.waitFor(60000));
+
+    XDebuggerTestUtil.waitForSwing();
+
+    assertFinished();
+
+    Assert.assertTrue(output(), allTestsCount() > 0);
+
+    after();
+
+    disposeProcess(myProcessHandler);
+  }
+
+  protected void configure(AbstractPythonTestRunConfiguration config) {
+  }
+
+  public void allTestsPassed() {
+    Assert.assertEquals(output(), 0, myTestProxy.getChildren(Filter.NOT_PASSED).size());
+    Assert.assertEquals(output(), 0, failedTestsCount());
+  }
+
+  public int failedTestsCount() {
+    return myTestProxy.collectChildren(NOT_SUIT.and(Filter.FAILED_OR_INTERRUPTED)).size();
+  }
+
+  public int passedTestsCount() {
+    return myTestProxy.collectChildren(NOT_SUIT.and(Filter.PASSED)).size();
+  }
+
+  public void assertFinished() {
+    Assert.assertTrue("State is " + myTestProxy.getMagnitudeInfo().getTitle() + "\n" + output(),
+                      myTestProxy.wasLaunched() && !myTestProxy.wasTerminated());
+  }
+
+  public int allTestsCount() {
+    return myTestProxy.collectChildren(NOT_SUIT).size();
+  }
+
+  public static final Filter<SMTestProxy> NOT_SUIT = new Filter<SMTestProxy>() {
+    @Override
+    public boolean shouldAccept(SMTestProxy test) {
+      return !test.isSuite();
+    }
+  };
+}