diff --git a/python/testSrc/com/jetbrains/env/PyEnvSufficiencyTest.java b/python/testSrc/com/jetbrains/env/PyEnvSufficiencyTest.java new file mode 100644 index 000000000000..83846a1bb5f5 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/PyEnvSufficiencyTest.java @@ -0,0 +1,51 @@ +package com.jetbrains.env; + +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 BASE_TAGS = + ImmutableList.builder().add("python3", "django", "jython", "ipython", "ipython011", "ipython012", "nose", "pytest").build(); + + public void testSufficiency() { + if (UsefulTestCase.IS_UNDER_TEAMCITY && IS_ENV_CONFIGURATION) { + Set tags = Sets.newHashSet(); + List roots = getPythonRoots(); + if (roots.size() == 0) { + return; // not on env agent + } + for (String root : roots) { + tags.addAll(loadEnvTags(root)); + } + + List 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 necessaryTags() { + if (SystemInfo.isWindows) { + return ImmutableList.builder().addAll(BASE_TAGS).add("iron").build(); + } + else { + return ImmutableList.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 index 000000000000..baabd5a5e5e1 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java @@ -0,0 +1,112 @@ +package com.jetbrains.env; + +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 myRoots; + + public PyEnvTaskRunner(List roots) { + myRoots = roots; + } + + public void runTask(PyTestTask testTask, String testName) { + boolean wasExecuted = false; + + List 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 tags, PyTestTask task) { + return isSuitableForTags(tags, task.getTags()); + } + + public static boolean isSuitableForTags(List envTags, Set taskTags) { + Set 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 index 000000000000..853a5504de63 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/PyEnvTestCase.java @@ -0,0 +1,201 @@ +package com.jetbrains.env; + +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 Assume manual. + * 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 getAvailableTags() { + final Collection allAvailableTags = new HashSet(); + 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 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 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 getPythonRoots() { + List 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 readVirtualEnvRoots(@NotNull String envs) { + List 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 loadEnvTags(String env) { + List 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 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 index 000000000000..f42fd9721c67 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/PyExecutionFixtureTestTask.java @@ -0,0 +1,174 @@ +package com.jetbrains.env; + +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 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 contentRoots = getContentRoots(); + for (String contentRoot : contentRoots) { + moduleFixtureBuilder.addContentRoot(getTestDataPath() + contentRoot); + } + } + } + ); + + + myFixture.setUp(); + myFixture.setTestDataPath(getTestDataPath()); + } + + protected List 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 implements MyModuleFixtureBuilder { + public MyModuleFixtureBuilderImpl(TestFixtureBuilder fixtureBuilder) { + super(new PlatformPythonModuleType(), fixtureBuilder); + } + + @Override + protected ModuleFixture instantiateFixture() { + return new ModuleFixtureImpl(this); + } + } + + public static class PlatformPythonModuleType extends PythonModuleTypeBase { + @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 { + + } +} diff --git a/python/testSrc/com/jetbrains/env/PyTestTask.java b/python/testSrc/com/jetbrains/env/PyTestTask.java new file mode 100644 index 000000000000..bfc1a688ebfa --- /dev/null +++ b/python/testSrc/com/jetbrains/env/PyTestTask.java @@ -0,0 +1,81 @@ +package com.jetbrains.env; + +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 getTags() { + return Sets.newHashSet(); + } +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoEnvTestTask.java b/python/testSrc/com/jetbrains/env/django/DjangoEnvTestTask.java new file mode 100644 index 000000000000..7e87e9fd8cb2 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoEnvTestTask.java @@ -0,0 +1,148 @@ +package com.jetbrains.env.django; + +import com.google.common.collect.ImmutableSet; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ui.UIUtil; +import com.jetbrains.django.facet.DjangoFacet; +import com.jetbrains.django.fixtures.DjangoTestCase; +import com.jetbrains.env.python.debug.PyDebuggerTask; +import com.jetbrains.python.fixtures.PyProfessionalTestCase; +import com.jetbrains.python.templateLanguages.TemplatesService; +import org.junit.Assert; + +import java.net.URL; +import java.net.URLConnection; +import java.util.Scanner; +import java.util.Set; + +/** + * @author traff + */ +public class DjangoEnvTestTask extends PyDebuggerTask { + + protected final int myPort; + + protected String getTestDataPath() { + return PyProfessionalTestCase.getProfessionalTestDataPath() + "/django/debug"; + } + + public DjangoEnvTestTask(String workingFolder, String scriptName, String scriptParameters, int port) { + setWorkingFolder(getTestDataPath() + workingFolder); + setScriptName(scriptName); + setScriptParameters(scriptParameters + port); + myPort = port; + } + + @Override + public void setUp(String testName) throws Exception { + super.setUp(testName); + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + public void run() { + Module module = myFixture.getModule(); + if (module != null && !DjangoFacet.isPresent(module)) { + DjangoTestCase.addDjangoFacet(module); + TemplatesService.getInstance(module).setTemplateLanguage(TemplatesService.DJANGO); + } + TemplatesService.getInstance(module).setTemplateLanguage(TemplatesService.DJANGO); + } + }); + } + + @Override + public void tearDown() throws Exception { + TemplatesService.getInstance(myFixture.getModule()).setTemplateLanguage(TemplatesService.NONE); + super.tearDown(); + } + + public static void checkModuleForFile(Project project, VirtualFile vFile) { + Module module = ModuleUtil.findModuleForFile(vFile, project); + Assert.assertNotNull(module); //should be in our module for correct breakpoints setting + Assert.assertTrue(DjangoFacet.isPresent(module)); + } + + static class Response { + private final String content; + + + public Response(String content) { + this.content = content; + } + + public void contains(String string) { + Assert.assertTrue("Cant find '" + string + "' in content: \n" + content, content.contains(string)); + } + + public static Response load(String address) { + StringBuilder sb = new StringBuilder(); + try { + Thread.sleep(500); + // Send data + URL url = new URL(address); + URLConnection conn = url.openConnection(); + conn.setDoOutput(true); + conn.setConnectTimeout(0); + conn.setReadTimeout(0); + conn.setUseCaches(false); + + // Get the response + Scanner s = new Scanner(conn.getInputStream()); + + + while (s.hasNextLine()) { + String line = s.nextLine(); + sb.append(line + "\n"); + // Process line... + } + } + catch (Exception e) { + sb = new StringBuilder(e.getMessage()); + } + return new Response(sb.toString()); + } + } + + protected static LoadingPage loadPage(final String url) { + final Ref response = Ref.create(null); + + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + response.set(Response.load("http://127.0.0.1:" + url)); + } + }); + thread.start(); + return new LoadingPage(thread, response); + } + + protected static class LoadingPage { + private Thread myThread; + private Ref myResponse; + + public LoadingPage(Thread thread, Ref response) { + myThread = thread; + myResponse = response; + } + + public void join() throws InterruptedException { + myThread.join(); + } + + public Response get() throws InterruptedException { + join(); + return myResponse.get(); + } + } + + @Override + public Set getTags() { + return ImmutableSet.of("django"); + } + + protected void waitForStart() throws InterruptedException { + waitForOutput("Development server is running", "Starting development server"); + } +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoManageTaskActionsTest.java b/python/testSrc/com/jetbrains/env/django/DjangoManageTaskActionsTest.java new file mode 100644 index 000000000000..ca3f2c23fd18 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoManageTaskActionsTest.java @@ -0,0 +1,68 @@ +package com.jetbrains.env.django; + +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.roots.ModuleRootModificationUtil; +import com.jetbrains.django.actions.manage.ManageTaskActions; +import com.jetbrains.django.manage.BaseCommand; +import com.jetbrains.django.testRunner.DjangoTestsConfigurationType; +import com.jetbrains.django.util.DjangoUtil; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.python.run.AbstractPythonRunConfiguration; +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +/** + * @author Ilya.Kazakevich + */ +public class DjangoManageTaskActionsTest extends PyEnvTestCase { + + /** + * Checks that "manage.py" UI displays correct commands + */ + public void testManageCommands() throws Exception { + runPythonTest(new DjangoManageCommandsTestTask()); + } + + + private static class DjangoManageCommandsTestTask extends DjangoPathTestTask { + @Nullable + @Override + public ConfigurationFactory getFactory() { + return DjangoTestsConfigurationType.getInstance().getConfigurationFactories()[0]; + } + + + @Override + protected void configure(final AbstractPythonRunConfiguration config) throws IOException { + super.configure(config); + final Module module = config.getModule(); + assert module != null : "Config has no module"; + ModuleRootModificationUtil.setModuleSdk(module, config.getSdk()); + + + Assert.assertNotNull("Django not found", DjangoUtil.getDjangoPath(config.getSdk())); + + final Collection expectedCommands = new HashSet(); + expectedCommands.add("runserver"); + expectedCommands.add("dbshell"); + expectedCommands.add("syncdb"); + if (DjangoUtil.isDjangoVersionAtLeast(myFixture.getModule(), 7)) { + expectedCommands.add("makemigrations"); + expectedCommands.add("migrate"); + } + + final List commands = ManageTaskActions.getInstance().getCommands(myFixture.getModule()); + + for (final BaseCommand command : commands) { + expectedCommands.remove(command.getCommand()); + } + Assert.assertTrue(String.format("Commands '%s' were not found between %s", expectedCommands, commands), expectedCommands.isEmpty()); + } + } +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoManageTestTask.java b/python/testSrc/com/jetbrains/env/django/DjangoManageTestTask.java new file mode 100644 index 000000000000..f476ac99aadb --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoManageTestTask.java @@ -0,0 +1,276 @@ +package com.jetbrains.env.django; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +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.process.ProcessOutputTypes; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.runners.ProgramRunner; +import com.intellij.execution.ui.ExecutionConsole; +import com.intellij.execution.ui.RunContentDescriptor; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Key; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.ui.UIUtil; +import com.intellij.xdebugger.XDebuggerTestUtil; +import com.jetbrains.django.facet.DjangoFacet; +import com.jetbrains.django.fixtures.DjangoTestCase; +import com.jetbrains.django.manage.DjangoManageTask; +import com.jetbrains.django.testRunner.DjangoTestsConfigurationType; +import com.jetbrains.django.ui.DjangoBundle; +import com.jetbrains.env.PyExecutionFixtureTestTask; +import com.jetbrains.python.PythonHelpersLocator; +import com.jetbrains.python.run.AbstractPythonRunConfiguration; +import com.jetbrains.python.run.PythonTracebackFilter; +import com.jetbrains.python.sdk.InvalidSdkException; +import com.jetbrains.python.sdkTools.SdkCreationType; +import com.jetbrains.python.templateLanguages.TemplatesService; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Assert; + +import java.io.IOException; +import java.util.Set; + +/** + * @author traff + */ +public abstract class DjangoManageTestTask extends PyExecutionFixtureTestTask { + private StringBuilder myOutput; + private StringBuilder myStdErr; + private StringBuilder myStdOut; + + private RunContentDescriptor myDescriptor; + private ProcessHandler myProcessHandler; + private ExecutionConsole myConsoleView; + private Sdk mySdk; + private RunContentExecutor myRunContentExecutor; + + @Override + protected abstract String getTestDataPath(); + + @Override + public void setUp(String testName) throws Exception { + super.setUp(testName); + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + public void run() { + Module module = myFixture.getModule(); + if (module != null && !DjangoFacet.isPresent(module)) { + DjangoTestCase.addDjangoFacet(module); + TemplatesService.getInstance(module).setTemplateLanguage(TemplatesService.DJANGO); + } + TemplatesService.getInstance(module).setTemplateLanguage(TemplatesService.DJANGO); + } + }); + } + + @Override + public void tearDown() throws Exception { + TemplatesService.getInstance(myFixture.getModule()).setTemplateLanguage(TemplatesService.NONE); + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + public void run() { + try { + if (myConsoleView != null) { + Disposer.dispose(myConsoleView); + myConsoleView = null; + } + if (myDescriptor != null) { + Disposer.dispose(myDescriptor); + myDescriptor = null; + } + if (myRunContentExecutor != null) { + Disposer.dispose(myRunContentExecutor); + } + DjangoManageTestTask.super.tearDown(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + + @Nullable + public abstract ConfigurationFactory getFactory(); + + @Override + public void runTestOn(final String sdkHome) throws Exception { + mySdk = createTempSdk(sdkHome, SdkCreationType.SDK_PACKAGES_ONLY); + before(); + + final Semaphore s = new Semaphore(); + s.down(); + + + myOutput = new StringBuilder(); + myStdErr = new StringBuilder(); + myStdOut = new StringBuilder(); + + runProcess(sdkHome, s); + + try { + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + try { + testing(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + Assert.assertTrue(s.waitFor(60000)); + XDebuggerTestUtil.waitForSwing(); + after(); + } + finally { + + disposeProcess(myProcessHandler); + } + } + + private void runProcess(final String sdkHome, final Semaphore s) throws ExecutionException, IOException, InvalidSdkException { + final ConfigurationFactory factory = getFactory(); + + if (factory == null) { // PythonTask (there is no run configuration) + final Module module = myFixture.getModule(); + final DjangoManageTask task = new DjangoManageTask(module, DjangoBundle.message("manage.run.tab.name"), mySdk); + + task.setWorkingDirectory(getWorkingFolder()); + task.setRunnerScript(PythonHelpersLocator.getHelperPath("pycharm/django_manage.py")); + ImmutableList.Builder parametersString = + new ImmutableList.Builder().add(getSubcommand()).add(getTestDataPath()); + + task.setParameters(parametersString.build()); + + myProcessHandler = task.createProcess(null); + + myProcessHandler.addProcessListener(createProcessListener()); + + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + public void run() { + final Project project = myFixture.getProject(); + myRunContentExecutor = new RunContentExecutor(project, myProcessHandler); + myRunContentExecutor + .withFilter(new PythonTracebackFilter(project)) + .run(); + } + }); + s.up(); + } + else { //run dango server and django test + final Project project = getProject(); + final RunnerAndConfigurationSettings settings = + RunManager.getInstance(project).createRunConfiguration("test", factory); + + final AbstractPythonRunConfiguration config = (AbstractPythonRunConfiguration)settings.getConfiguration(); + config.addSourceRoots(DjangoTestsConfigurationType.getInstance().getDjangoTestsFactory().equals(factory)); + config.addContentRoots(DjangoTestsConfigurationType.getInstance().getDjangoTestsFactory().equals(factory)); + config.setSdkHome(sdkHome); + config.setWorkingDirectory(getWorkingFolder()); + + new WriteAction() { + @Override + protected void run(Result result) throws Throwable { + configure(config); + + 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); + + 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(createProcessListener()); + myConsoleView = myDescriptor.getExecutionConsole(); + s.up(); + } + }); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + } + + private ProcessAdapter createProcessListener() { + return new ProcessAdapter() { + @Override + public void onTextAvailable(ProcessEvent event, Key outputType) { + myOutput.append(event.getText()); + if (outputType == ProcessOutputTypes.STDOUT) { + myStdOut.append(event.getText()); + } + else if (outputType == ProcessOutputTypes.STDERR) { + myStdErr.append(event.getText()); + } + } + }; + } + + protected abstract String getSubcommand(); + + protected void configure(AbstractPythonRunConfiguration config) throws IOException { + } + + @NotNull + protected String output() { + return myOutput.toString(); + } + + @NotNull + protected String stderr() { + return myStdErr.toString(); + } + + @NotNull + protected String stdout() { + return myStdOut.toString(); + } + + public void waitForOutput(@NotNull final String string) throws InterruptedException { + int count = 0; + while (!output().contains(string)) { + if (count > 10) { + Assert.fail("'" + string + "'" + " is not present in output.\n" + output()); + } + Thread.sleep(2000); + count++; + } + } + + @Override + public Set getTags() { + return ImmutableSet.of("django"); + } +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoPathTest.java b/python/testSrc/com/jetbrains/env/django/DjangoPathTest.java new file mode 100644 index 000000000000..c98d26f9ca60 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoPathTest.java @@ -0,0 +1,100 @@ +package com.jetbrains.env.django; + +import com.google.common.collect.Lists; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.openapi.util.io.FileUtil; +import com.jetbrains.django.run.DjangoServerRunConfiguration; +import com.jetbrains.django.run.DjangoServerRunConfigurationType; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.python.run.AbstractPythonRunConfiguration; +import junit.framework.Assert; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.ArrayList; + +/** + * User : catherine + */ +public class DjangoPathTest extends PyEnvTestCase { + + /** + * This string is printed after printing sys.path in djangoPath/settings.py + */ + public static final String THE_END_OF_SYS_PATH = "The end of sys.path"; + + public void testRunserverPath() throws IOException { + runPythonTest(new DjangoPathTestTask() { + + @Override + protected void configure(AbstractPythonRunConfiguration config) throws IOException { + final int[] ports = DjangoTemplateDebuggerTest.findFreePorts(2); + ((DjangoServerRunConfiguration)config).setPort(ports[1]); + ((DjangoServerRunConfiguration)config).setRunNoReload(true); + } + + @Override + public ConfigurationFactory getFactory() { + return DjangoServerRunConfigurationType.getInstance().getConfigurationFactories()[0]; + } + + + public void testing() throws Exception { + waitForOutput(THE_END_OF_SYS_PATH); + + doTest(output(), getTestDataPath()); + } + }); + } + + private static void doTest(String output, String testDataPath) { + final String[] splittedOutput = output.split("\\n"); + final ArrayList outputList = Lists.newArrayList(); + for (String s : splittedOutput) { + if (s.equals(THE_END_OF_SYS_PATH)) { + break; + } + outputList.add(norm(s)); + } + + testDataPath = norm(testDataPath); + + + Assert.assertEquals(testDataPath, outputList.get(1)); + + assertEquals(outputList.indexOf(testDataPath), + outputList.lastIndexOf(testDataPath)); + } + + private static String norm(String testDataPath) { + return FileUtil.toSystemIndependentName(testDataPath); + } + + public void testManagePath() throws IOException { + runPythonTest(new DjangoPathTestTask() { + + @Nullable + @Override + public ConfigurationFactory getFactory() { + return null; + } + + public void testing() throws Exception { + waitForOutput(THE_END_OF_SYS_PATH); + + final String[] splittedOutput = output().split("\\n"); + final ArrayList outputList = Lists.newArrayList(); + for (String s : splittedOutput) { + if (s.equals(THE_END_OF_SYS_PATH)) { + break; + } + outputList.add(s); + } + + assertEquals(getTestDataPath(), outputList.get(1)); + assertEquals(outputList.indexOf(getTestDataPath()), + outputList.lastIndexOf(getTestDataPath())); + } + }); + } +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoPathTestTask.java b/python/testSrc/com/jetbrains/env/django/DjangoPathTestTask.java new file mode 100644 index 000000000000..6df0f0a19309 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoPathTestTask.java @@ -0,0 +1,19 @@ +package com.jetbrains.env.django; + +import com.jetbrains.python.fixtures.PyProfessionalTestCase; + +/** + * User : catherine + */ +public abstract class DjangoPathTestTask extends DjangoManageTestTask { + + @Override + protected String getTestDataPath() { + return PyProfessionalTestCase.getProfessionalTestDataPath() + "/django/path/djangoPath"; + } + + @Override + protected String getSubcommand() { + return "validate"; + } +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoProjectCreationTest.java b/python/testSrc/com/jetbrains/env/django/DjangoProjectCreationTest.java new file mode 100644 index 000000000000..7d977dc9cebd --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoProjectCreationTest.java @@ -0,0 +1,144 @@ +package com.jetbrains.env.django; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.Sets; +import com.intellij.execution.ExecutionException; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.UsefulTestCase; +import com.jetbrains.django.DjangoNames; +import com.jetbrains.django.model.DjangoAdmin; +import com.jetbrains.django.util.DjangoUtil; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.PyExecutionFixtureTestTask; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyUtil; +import com.jetbrains.python.psi.impl.blockEvaluator.PyBlockEvaluator; +import com.jetbrains.python.sdkTools.SdkCreationType; +import org.hamcrest.Matchers; +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** + * Tests projection creation for Django + * + * @author Ilya.Kazakevich + */ +public class DjangoProjectCreationTest extends PyEnvTestCase { + + private static final String ROOT_FOLDER_NAME = "content_root"; + private static final String WEB_SITE_NAME = "myWebSite"; + private static final String DJANGO_TAG = "django"; + private static final MyDjangoProjectCreationTest.Canonicator CANONICATOR = new MyDjangoProjectCreationTest.Canonicator(); + + public DjangoProjectCreationTest() { + super(DJANGO_TAG); + } + + + // TODO: Doc + public void testProjectWithTemplates() throws Exception { + runPythonTest(new MyDjangoProjectCreationTest()); + } + + private static class MyDjangoProjectCreationTest extends PyExecutionFixtureTestTask { + @Override + public void runTestOn(@NotNull final String sdkHome) throws Exception { + final Sdk sdk = createTempSdk(sdkHome, SdkCreationType.SDK_PACKAGES_ONLY); + final String djangoPath = DjangoUtil.getDjangoPath(sdk); + assert djangoPath != null : "No django found under " + sdkHome; + + final VirtualFile contentRoot = myFixture.getTempDirFixture().findOrCreateDir(ROOT_FOLDER_NAME); + final String templateDir = myFixture.getTempDirFixture().findOrCreateDir(ROOT_FOLDER_NAME + "/templates_dir").getCanonicalPath(); + assert templateDir != null : "Failed to create template dirs"; + + UsefulTestCase.edt(new Runnable() { + @Override + public void run() { + + new WriteAction() { + @Override + protected void run(@NotNull Result result) throws Throwable { + try { + DjangoAdmin.createProject( + myFixture.getProject(), + sdk, + contentRoot, + WEB_SITE_NAME, + "myMainApp", + templateDir, + djangoPath, + false, + false + ); + } + catch (final ExecutionException ex) { + Assert.fail(ex.toString()); + } + } + }.execute(); + } + }); + + final String pathToSettingsPy; + if (DjangoAdmin.isDjangoVersionAtLeast(sdk, "1.4")) { + pathToSettingsPy = ROOT_FOLDER_NAME + '/' + WEB_SITE_NAME + '/' + DjangoNames.SETTINGS_FILE; + } + else { + pathToSettingsPy = ROOT_FOLDER_NAME + '/' + DjangoNames.SETTINGS_FILE; + } + + final PyFile settingsPy = + PyUtil.as(myFixture.configureByFile(pathToSettingsPy), PyFile.class); + Assert.assertNotNull(DjangoNames.SETTINGS_FILE + " not created", settingsPy); + Assert.assertEquals("Bad file name for settings", DjangoNames.SETTINGS_FILE, settingsPy.getName()); + + + final PyBlockEvaluator evaluator = new PyBlockEvaluator(); + + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + evaluator.evaluate(settingsPy); + } + }); + + + final List templateDirsFromConfig = evaluator.getValueAsStringList(DjangoNames.TEMPLATE_DIRS_SETTING); + Assert.assertThat(String.format("Failed to find %s in %s", DjangoNames.TEMPLATE_DIRS_SETTING, settingsPy), templateDirsFromConfig, + Matchers.allOf(Matchers.notNullValue(), Matchers.not(Matchers.empty()))); + + // Canoncial path is used to prevent ".." + Assert.assertThat("Wrong template dirs in config", Collections2.transform(templateDirsFromConfig, CANONICATOR), + Matchers.contains(CANONICATOR.apply(templateDir))); + } + + @Override + public Set getTags() { + return Sets.newHashSet(DJANGO_TAG); + } + + // Makes canoncial path from any path + private static class Canonicator implements Function { + + @Override + public String apply(final String input) { + try { + return new File(input).getCanonicalPath(); + } + catch (final IOException e) { + throw new AssertionError("Errror with file " + input + " : " + e); + } + } + } + } +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoTemplateBreakpointsTest.java b/python/testSrc/com/jetbrains/env/django/DjangoTemplateBreakpointsTest.java new file mode 100644 index 000000000000..c9260dfece65 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoTemplateBreakpointsTest.java @@ -0,0 +1,63 @@ +package com.jetbrains.env.django; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.fixtures.CodeInsightTestFixture; +import com.intellij.xdebugger.XDebuggerUtil; +import com.jetbrains.django.fixtures.DjangoTestCase; +import junit.framework.Assert; + +/** + * @author traff + */ +public class DjangoTemplateBreakpointsTest extends DjangoTestCase { + @Override + protected String getTestDataPath() { + return super.getTestDataPath() + "/django/debug/"; + } + + public void testCanSetBreakpoints() { + prepareProject("djangoDebug"); + inFile("djangoDebug/templates/test1.html") + .notCan(0) + .notCan(1) + .notCan(2) + .can(3) + .notCan(4) + .can(5) + .can(6) + .notCan(7) + .can(8) + .can(9) + .notCan(10) + .notCan(11) + .notCan(12); + } + + private OpenedTemplate inFile(String fileName) { + return new OpenedTemplate(fileName, myFixture); + } + + private static class OpenedTemplate { + private String myFile; + private CodeInsightTestFixture myFixture; + + private OpenedTemplate(String file, CodeInsightTestFixture fixture) { + myFile = file; + myFixture = fixture; + } + + private OpenedTemplate can(int line) { + Assert.assertTrue("at line " + line, XDebuggerUtil.getInstance().canPutBreakpointAt(myFixture.getProject(), findFile(), line)); + return this; + } + + private VirtualFile findFile() { + return myFixture.findFileInTempDir(myFile); + } + + private OpenedTemplate notCan(int line) { + Assert.assertFalse("at line " + line, XDebuggerUtil.getInstance().canPutBreakpointAt(myFixture.getProject(), findFile(), line)); + return this; + } + } +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoTemplateDebuggerTest.java b/python/testSrc/com/jetbrains/env/django/DjangoTemplateDebuggerTest.java new file mode 100644 index 000000000000..1cda9ed3a533 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoTemplateDebuggerTest.java @@ -0,0 +1,361 @@ +package com.jetbrains.env.django; + +import com.google.common.collect.Sets; +import com.intellij.testFramework.fixtures.IdeaProjectTestFixture; +import com.intellij.xdebugger.XDebuggerTestUtil; +import com.jetbrains.django.util.VirtualFileUtil; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.python.debugger.django.DjangoExceptionBreakpointProperties; +import com.jetbrains.python.debugger.django.DjangoExceptionBreakpointType; + +import java.io.IOException; +import java.net.ServerSocket; +import java.util.Set; + +/** + * @author traff + */ +public class DjangoTemplateDebuggerTest extends PyEnvTestCase { + + public void testBreakpointStopAndEval() throws IOException { + final int[] ports = findFreePorts(2); + runPythonTest(new BreakpointStopAndEvalTask("/djangoDebug", "manage.py", "runserver --noreload ", ports[1])); + } + + private static class BreakpointStopAndEvalTask extends DjangoEnvTestTask { + public BreakpointStopAndEvalTask(String workingFolder, String scriptName, String scriptParameters, int port) { + super(workingFolder, scriptName, scriptParameters, port); + } + + @Override + public void before() throws Exception { + //setShouldPrintOutput(true); + String file = getFilePath("/templates/test1.html"); + checkModuleForFile(getProject(), VirtualFileUtil.findFile(file)); + + toggleBreakpoint(file, 6); + } + + @Override + public void testing() throws Exception { + waitForStart(); + + DjangoEnvTestTask.LoadingPage page = loadPage(myPort + "/test1"); + + waitForPause(); + + eval("x").hasValue("1"); + + resume(); + + waitForPause(); + + eval("x").hasValue("2"); + + resume(); + + waitForPause(); + + eval("x").hasValue("3"); + + resume(); + + page.get().contains("TemplateDebugging"); + } + } + + /* TODO: uncomment after fixing http://youtrack.jetbrains.com/issue/PY-13465 + public void testSetVal() throws IOException { +//setShouldPrintOutput(true); + final int[] ports = findFreePorts(2); + runPythonTest(new DjangoEnvTestTask("/djangoDebug", "manage.py", "runserver --noreload ", ports[1]) { + @Override + public void before() throws Exception { + String file = getFilePath("/templates/test1.html"); + checkModuleForFile(getProject(), VirtualFileUtil.findFile(file)); + + toggleBreakpoint(file, 6); + } + + @Override + public void testing() throws Exception { + waitForStart(); + + LoadingPage page = loadPage(myPort + "/test1"); + + waitForPause(); + + eval("x").hasValue("1"); + eval("name").hasValue("'TemplateDebugging'"); + + setVal("name", "'TemplateDjangoDebugging'"); + + resume(); + + waitForPause(); + + eval("x").hasValue("2"); + eval("name").hasValue("'TemplateDjangoDebugging'"); + + resume(); + + waitForPause(); + + eval("x").hasValue("3"); + eval("name").hasValue("'TemplateDjangoDebugging'"); + + resume(); + + page.get().contains("templatedjangodebugging"); + } + + + }); + } + */ + + public void testVariableDoesNotExistExceptionBreak() throws IOException { +//setShouldPrintOutput(true); + final int[] ports = findFreePorts(2); + runPythonTest(new DjangoEnvTestTask("/djangoDebug", "manage.py", "runserver --noreload ", ports[1]) { + @Override + public void before() throws Exception { + addDjangoExceptionBreakpoint(myFixture); + } + + @Override + public void testing() throws Exception { + waitForStart(); + + LoadingPage page = loadPage(myPort + "/test2"); + + waitForPause(); + + eval("x").hasValue("1"); + + resume(); + + waitForPause(); + + eval("x").hasValue("2"); + + resume(); + + waitForPause(); + + eval("x").hasValue("3"); + + resume(); + + page.get().contains("templatedebugging"); + } + }); + } + + public void testTemplateDoseNotExistsExceptionBreak() throws IOException { +//setShouldPrintOutput(true); + final int[] ports = findFreePorts(2); + runPythonTest(new DjangoEnvTestTask("/djangoDebug", "manage.py", "runserver --noreload ", ports[1]) { + @Override + public void before() throws Exception { + addDjangoExceptionBreakpoint(myFixture); + } + + @Override + public void testing() throws Exception { + waitForStart(); + + LoadingPage page = loadPage(myPort + "/test3"); + + waitForPause(); + + resume(); + + page.get().contains("Server returned HTTP response code: 500"); + } + }); + } + + public void testConditionalBreakpointStop() throws IOException { + + final int[] ports = findFreePorts(2); + runPythonTest(new DjangoEnvTestTask("/djangoDebug", "manage.py", "runserver --noreload ", ports[1]) { + @Override + public void before() throws Exception { + //setShouldPrintOutput(true); + String file = getFilePath("/templates/test1.html"); + checkModuleForFile(getProject(), VirtualFileUtil.findFile(file)); + + toggleBreakpoint(file, 6); + XDebuggerTestUtil.setBreakpointCondition(getProject(), 6, "x == 1 or x == 3"); + } + + @Override + public void testing() throws Exception { + waitForStart(); + + LoadingPage page = loadPage(myPort + "/test1"); + + waitForPause(); + + eval("x").hasValue("1"); + + resume(); + + waitForPause(); + + eval("x").hasValue("3"); + + resume(); + + page.get().contains("TemplateDebugging"); + } + }); + } + + public void testBreakpointStopAndStepOver() throws IOException { + + final int[] ports = findFreePorts(2); + runPythonTest(new DjangoEnvTestTask("/djangoDebug", "manage.py", "runserver --noreload ", ports[1]) { + @Override + public void before() throws Exception { + //setShouldPrintOutput(true); + String file = getFilePath("/templates/test4.html"); + checkModuleForFile(getProject(), VirtualFileUtil.findFile(file)); + + toggleBreakpoint(file, 6); + } + + @Override + public void testing() throws Exception { + waitForStart(); + + loadPage(ports[1] + "/test4"); + + waitForPause(); + + eval("x").hasValue("1"); + + stepOver(); + + waitForPause(); + + stepOver(); + + waitForPause(); + + eval("y").hasValue("1"); + + resume(); + } + }); + } + + public void testBreakpointLogExpression() throws IOException { + + final int[] ports = findFreePorts(2); + runPythonTest(new DjangoEnvTestTask("/djangoDebug", "manage.py", "runserver --noreload ", ports[1]) { + @Override + public void before() throws Exception { + //setShouldPrintOutput(true); + String file = getFilePath("/templates/test1.html"); + checkModuleForFile(getProject(), VirtualFileUtil.findFile(file)); + + toggleBreakpoint(file, 6); + XDebuggerTestUtil.setBreakpointLogExpression(getProject(), 6, "'x = %d'%x"); + + useLongTimeout(); + } + + @Override + public void testing() throws Exception { + waitForStart(); + + loadPage(ports[1] + "/test1"); + + waitForPause(); + resume(); + waitForOutput("x = 2"); + } + }); + } + + + public void testStepInto() throws IOException { + + final int[] ports = findFreePorts(2); + runPythonTest(new DjangoEnvTestTask("/djangoDebug", "manage.py", "runserver --noreload ", ports[1]) { + @Override + public void before() throws Exception { + //setShouldPrintOutput(true); + String file = getFilePath("/templates/test5.html"); + checkModuleForFile(getProject(), VirtualFileUtil.findFile(file)); + + toggleBreakpoint(file, 5); + } + + @Override + public void testing() throws Exception { + waitForStart(); + + loadPage(ports[1] + "/test5"); + + waitForPause(); + stepInto(); + waitForPause(); + stepOver(); + waitForPause(); + + eval("res").hasValue("'Result'"); + stepOver(); + waitForPause(); + eval("name").hasValue("'TemplateDebugging5'"); + } + }); + } + + private static void addDjangoExceptionBreakpoint(IdeaProjectTestFixture fixture) { + XDebuggerTestUtil.addBreakpoint(fixture.getProject(), DjangoExceptionBreakpointType.class, new DjangoExceptionBreakpointProperties()); + } + + public static int[] findFreePorts(int count) + throws IOException { + ServerSocket[] servers = new ServerSocket[count]; + int[] port = new int[count]; + + for (int i = 0; i < count; i++) { + servers[i] = new ServerSocket(0); + port[i] = servers[i].getLocalPort(); + } + + for (int i = 0; i < count; i++) { + servers[i].close(); + } + + return port; + } + + + public void testBreakpointStopAndEvalInAutoReloadMode() throws IOException { + final int[] ports = findFreePorts(4); + BreakpointStopAndEvalTask testTask = new BreakpointStopAndEvalTask("/djangoDebug", "manage.py", "runserver ", ports[3]) { + @Override + public void before() throws Exception { + super.before(); + useLongTimeout(); + } + + @Override + public Set getTags() { + Set tags = Sets.newHashSet(super.getTags()); + tags.add("-django13"); //Django 1.3 doesn't work in reload mode on Unix + return tags; + } + }; + //testTask.setShouldPrintOutput(true); + testTask.setMultiprocessDebug(true); + runPythonTest(testTask); + } +} + + diff --git a/python/testSrc/com/jetbrains/env/django/DjangoTestRunnerTest.java b/python/testSrc/com/jetbrains/env/django/DjangoTestRunnerTest.java new file mode 100644 index 000000000000..9be764dd6eb6 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoTestRunnerTest.java @@ -0,0 +1,155 @@ +package com.jetbrains.env.django; + +import com.google.common.collect.Lists; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.util.ui.UIUtil; +import com.jetbrains.django.facet.DjangoFacet; +import com.jetbrains.django.facet.DjangoFacetConfiguration; +import com.jetbrains.django.testRunner.DjangoTestsConfigurationType; +import com.jetbrains.django.testRunner.DjangoTestsRunConfiguration; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.python.packaging.PyExternalProcessException; +import com.jetbrains.python.packaging.PyPackage; +import com.jetbrains.python.packaging.PyPackageManager; +import com.jetbrains.python.packaging.PyRequirement; +import com.jetbrains.python.run.AbstractPythonRunConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * User : ktisha + */ +public class DjangoTestRunnerTest extends PyEnvTestCase { + + private static void doTest(@NotNull final String output, @NotNull final String testDataPath) { + final String[] splittedOutput = output.split("\\n"); + final ArrayList outputList = Lists.newArrayList(); + boolean add = false; + for (String s : splittedOutput) { + if ("sys.path end".equals(s)) add = false; + if (add) outputList.add(norm(s)); + if ("sys.path start".equals(s)) add = true; + } + assertEquals(outputList.indexOf(testDataPath), + outputList.lastIndexOf(testDataPath)); + } + + private static String norm(String testDataPath) { + return FileUtil.toSystemIndependentName(testDataPath); + } + + private void doTest(@Nullable final String projectRoot, @Nullable final String settings, + @Nullable final String markAsSource, @Nullable final String customSettings) { + final String name = getTestName(false); + runPythonTest(new DjangoTestRunnerTestTask() { + @Override + public ConfigurationFactory getFactory() { + return DjangoTestsConfigurationType.getInstance().getConfigurationFactories()[0]; + } + + @Override + protected void configure(AbstractPythonRunConfiguration config) { + String target = "myapp.SimpleTest"; + try { + final PyPackage django = PyPackageManager.getInstance(config.getSdk()).findInstalledPackage("django"); + if (django != null) { + if (django.matches(PyRequirement.fromStringGuaranteed("django>=1.6a"))) { + target = "myapp.tests.SimpleTest"; + } + } + } + catch (PyExternalProcessException ignored) { + } + + ((DjangoTestsRunConfiguration)config).setTarget(target); + if (customSettings != null) + ((DjangoTestsRunConfiguration)config).setSettingsFile(getTestDataPath() + customSettings); + ((DjangoTestsRunConfiguration)config).useCustomSettings(true); + } + + @Override + protected List getContentRoots() { + return markAsSource == null ? Lists.newArrayList() : Lists.newArrayList(markAsSource); + } + + @Override + public void before() throws Exception { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + public void run() { + final Module module = myFixture.getModule(); + final DjangoFacet djangoFacet = DjangoFacet.getInstance(module); + if (djangoFacet != null && projectRoot != null && settings != null) { + final DjangoFacetConfiguration configuration = djangoFacet.getConfiguration(); + configuration.setProjectRootPath(getTestDataPath() + projectRoot); + configuration.setSettingsFilePath(settings); + } + } + }); + } + + public void testing() throws Exception { + waitForOutput("Process finished with exit code 0"); + doTest(output(), norm(getTestDataPath())); + } + + @Override + String getTestName() { + return name; + } + }); + } + + public void testDjango11() { + doTest(); + } + + public void testDjango12() { + doTest("", "settings.py", "", null); + } + + public void testDjango13() { + doTest(); + } + + private void doTest() { + doTest(null, null, null, null); + } + + public void testDjango14() { + doTest("", "Django14/settings.py", null, null); + } + + public void testDjango15() { + doTest("", "Django15/settings.py", null, null); + } + + public void testRoot13() { + doTest("/Django13", "settings.py", null, null); + } + + public void testRoot() { + doTest("/DjangoRoot", "DjangoRoot/settings.py", null, null); + } + + public void testRootMarked() { + doTest("/DjangoRoot", "DjangoRoot/settings.py", "/DjangoRoot", null); + } + + public void testSameNamedDir() { + doTest("/web/SameNamedDir", "settings.py", null, null); + } + + public void testCustomSettings() { + doTest(null, null, null, "/custom_settings.py"); + } + + public void testInstalledApps() { + doTest("/InstalledApps", "/config/settings/installedapps.py", null, "/config/settings/installedapps.py"); + } + +} diff --git a/python/testSrc/com/jetbrains/env/django/DjangoTestRunnerTestTask.java b/python/testSrc/com/jetbrains/env/django/DjangoTestRunnerTestTask.java new file mode 100644 index 000000000000..7b6fe8b9f6c2 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/django/DjangoTestRunnerTestTask.java @@ -0,0 +1,21 @@ +package com.jetbrains.env.django; + +import com.jetbrains.python.fixtures.PyProfessionalTestCase; + +/** + * User : ktisha + */ +public abstract class DjangoTestRunnerTestTask extends DjangoManageTestTask { + + @Override + protected String getTestDataPath() { + return PyProfessionalTestCase.getProfessionalTestDataPath() + "/django/testRunner/" + getTestName(); + } + + abstract String getTestName(); + + @Override + protected String getSubcommand() { + return "validate"; + } +} diff --git a/python/testSrc/com/jetbrains/env/python/IPythonConsoleTest.java b/python/testSrc/com/jetbrains/env/python/IPythonConsoleTest.java new file mode 100644 index 000000000000..a039344451d4 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/IPythonConsoleTest.java @@ -0,0 +1,100 @@ +package com.jetbrains.env.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.PyEnvTestCase; +import com.jetbrains.env.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 getErrors(PsiFile psi) { //TODO: NotNull? + if (!PsiTreeUtil.hasErrorElements(psi)) { + return Collections.emptyList(); + } + + return Collections2.transform(PsiTreeUtil.findChildrenOfType(psi, PsiErrorElement.class), new Function() { + @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 getTags() { + return ImmutableSet.of("-ipython"); + } + }); + } + + private static class IPythonTask extends PyConsoleTask { + @Override + public Set 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 index 000000000000..8e3b75dd56a0 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/PyPackageRequirementsInspectionTest.java @@ -0,0 +1,83 @@ +package com.jetbrains.env.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.PyEnvTestCase; +import com.jetbrains.env.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 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 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 index 000000000000..54afb702322b --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/PyPackagingTest.java @@ -0,0 +1,164 @@ +package com.jetbrains.env.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.PyEnvTestCase; +import com.jetbrains.env.PyExecutionFixtureTestTask; +import com.jetbrains.env.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 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 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 packages1 = manager.getPackages(); + // TODO: Install Markdown from a local file + manager.install(list(PyRequirement.fromString("Markdown<2.2"), + new PyRequirement("httplib2")), Collections.emptyList()); + final List 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 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 packages) { + for (PyPackage pkg : packages) { + if (name.equals(pkg.getName())) { + return pkg; + } + } + return null; + } + + private static List list(T... xs) { + return Arrays.asList(xs); + } + + + private abstract static class PyPackagingTestTask extends PyExecutionFixtureTestTask { + @Override + public Set 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 index 000000000000..4134b1697851 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/PythonConsoleTest.java @@ -0,0 +1,130 @@ +package com.jetbrains.env.python; + +import com.google.common.collect.Sets; +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.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 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 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 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 index 000000000000..4372715371eb --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/PythonDebuggerTest.java @@ -0,0 +1,579 @@ +package com.jetbrains.env.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.PyEnvTestCase; +import com.jetbrains.env.python.debug.PyDebuggerTask; +import com.jetbrains.env.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() { + @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 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 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 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 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 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 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 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 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 index 000000000000..273cb3f94011 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/PythonGeneratorTest.java @@ -0,0 +1,24 @@ +package com.jetbrains.env.python; + +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.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 index 000000000000..c6b150f31359 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/PythonSkeletonsTest.java @@ -0,0 +1,202 @@ +package com.jetbrains.env.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.PyEnvTestCase; +import com.jetbrains.env.PyExecutionFixtureTestTask; +import com.jetbrains.env.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. + *

+ * 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 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 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 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 index 000000000000..9a242934042d --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/console/PyConsoleTask.java @@ -0,0 +1,380 @@ +package com.jetbrains.env.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.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 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 getCompoundValueChildren(PyDebugValue value) throws PyDebuggerException { + XValueChildrenList list = myCommunication.loadVariable(value); + List result = Lists.newArrayList(); + for (int i = 0; i> 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 index 000000000000..7de13d1ebed6 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/debug/PyDebuggerTask.java @@ -0,0 +1,211 @@ +package com.jetbrains.env.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() { + @Override + protected void run(@NotNull Result 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 index 000000000000..a6f9dd68971d --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/dotNet/PyIronPythonTest.java @@ -0,0 +1,145 @@ +package com.jetbrains.env.python.dotNet; + +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.application.Result; +import com.jetbrains.env.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 index 000000000000..e10916cbe7de --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/dotNet/SkeletonTestTask.java @@ -0,0 +1,142 @@ +package com.jetbrains.env.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.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 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 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 index 000000000000..e56e6203b98d --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/dotNet/package-info.java @@ -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.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 index 000000000000..411b6049350a --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonDocTestingTest.java @@ -0,0 +1,66 @@ +package com.jetbrains.env.python.testing; + +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.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 index 000000000000..33250ae17fa4 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonNoseTestingTest.java @@ -0,0 +1,33 @@ +package com.jetbrains.env.python.testing; + +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.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 index 000000000000..ab3dee60c5b0 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java @@ -0,0 +1,33 @@ +package com.jetbrains.env.python.testing; + +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.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 index 000000000000..682eb8160285 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonUnitTestingTest.java @@ -0,0 +1,78 @@ +package com.jetbrains.env.python.testing; + +import com.jetbrains.env.PyEnvTestCase; +import com.jetbrains.env.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/remote/PyRemoteDebuggerTask.java b/python/testSrc/com/jetbrains/env/remote/PyRemoteDebuggerTask.java new file mode 100644 index 000000000000..844dd61e02a0 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/remote/PyRemoteDebuggerTask.java @@ -0,0 +1,226 @@ +package com.jetbrains.env.remote; + +import com.intellij.execution.*; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.executors.DefaultDebugExecutor; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.process.ProcessOutput; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.openapi.application.ApplicationManager; +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.util.ui.UIUtil; +import com.intellij.xdebugger.*; +import com.jetbrains.env.python.debug.PyBaseDebuggerTask; +import com.jetbrains.python.PythonHelpersLocator; +import com.jetbrains.python.debugger.remote.*; +import com.jetbrains.python.debugger.remote.vfs.PyRemotePositionConverter; +import com.jetbrains.python.sdk.PySdkUtil; +import com.jetbrains.python.sdk.PythonEnvUtil; +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 PyRemoteDebuggerTask extends PyBaseDebuggerTask { + private Semaphore mySessionInitializedSemaphore; + + public PyRemoteDebuggerTask() { + } + + public PyRemoteDebuggerTask(String workingFolder, String scriptName, String scriptParameters) { + setWorkingFolder(getTestDataPath() + workingFolder); + setScriptName(scriptName); + setScriptParameters(scriptParameters); + } + + public PyRemoteDebuggerTask(String workingFolder, String scriptName) { + this(workingFolder, scriptName, null); + } + + public void runTestOn(final String sdkHome) throws Exception { + final Project project = getProject(); + + final ConfigurationFactory factory = PyRemoteDebugConfigurationType.getInstance().getConfigurationFactories()[0]; + + + final RunnerAndConfigurationSettings settings = + RunManager.getInstance(project).createRunConfiguration("test", factory); + + + PyRemoteDebugConfiguration config = (PyRemoteDebugConfiguration)settings.getConfiguration(); + + + 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 PyRemoteDebugRunner runner = (PyRemoteDebugRunner)ProgramRunnerUtil.getRunner(DefaultDebugExecutor.EXECUTOR_ID, settings); + Assert.assertTrue(runner.canRun(DefaultDebugExecutor.EXECUTOR_ID, config)); + + final Executor executor = DefaultDebugExecutor.getDebugExecutorInstance(); + final ExecutionEnvironment env = new ExecutionEnvironment(executor, runner, settings, project); + + + final PyRemoteDebugCommandLineState pyState = (PyRemoteDebugCommandLineState)config.getState(executor, env); + assert pyState != null; + + final ServerSocket serverSocket; + final PyRemoteDebugConfiguration conf = (PyRemoteDebugConfiguration)env.getRunProfile(); + try { + //noinspection SocketOpenedButNotSafelyClosed + serverSocket = new ServerSocket(conf.getPort()); + } + catch (IOException e) { + throw new ExecutionException("Failed to find free socket port", e); + } + + setProcessCanTerminate(false); + + mySessionInitializedSemaphore = new Semaphore(0); + + UIUtil.invokeLaterIfNeeded(new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + try { + final ExecutionResult res = + pyState.execute(executor, runner); + + + mySession = XDebuggerManager.getInstance(project). + startSession(runner, env, env.getContentToReuse(), new XDebugProcessStarter() { + @NotNull + public XDebugProcess start(@NotNull final XDebugSession session) { + myDebugProcess = + new PyRemoteDebugProcess(session, serverSocket, res.getExecutionConsole(), + res.getProcessHandler(), null); + myDebugProcess.setPositionConverter(new PyRemotePositionConverter(myDebugProcess, conf.getMappingSettings())); + + 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()); + } + } + }); + + myPausedSemaphore = new Semaphore(0); + + myTerminateSemaphore = new Semaphore(0); + + return myDebugProcess; + } + }); + + mySession.addSessionListener(new XDebugSessionAdapter() { + @Override + public void sessionPaused() { + if (myPausedSemaphore != null) { + myPausedSemaphore.release(); + } + } + }); + + mySessionInitializedSemaphore.release(); + } + catch (ExecutionException e1) { + throw new RuntimeException(e1); + } + } + }); + } + }); + + before(); + + new Thread(new Runnable() { + @Override + public void run() { + while (myDebugProcess == null || !myDebugProcess.isWaitingForConnection()) { + try { + Thread.sleep(300); + } + catch (InterruptedException e) { + } + } + + ProcessOutput output = + PySdkUtil + .getProcessOutput(getWorkingFolder(), new String[]{sdkHome, getScriptPath(), Integer.toString(serverSocket.getLocalPort())}, + new String[]{ + PythonEnvUtil.PYTHONUNBUFFERED + "=x", + PythonEnvUtil.PYTHONPATH + "=" + PythonHelpersLocator.getHelpersRoot()}, 0); + checkOutput(output); + } + }).start(); + + + OutputPrinter myOutputPrinter = null; + if (shouldPrintOutput) + + { + myOutputPrinter = new OutputPrinter(); + myOutputPrinter.start(); + } + + + waitFor(mySessionInitializedSemaphore); + + doTest(myOutputPrinter); + } + + protected void checkOutput(ProcessOutput output) { + } + + protected void stopDebugServer() throws InterruptedException { + if (myDebugProcess != null) { + ProcessHandler h = myDebugProcess.getProcessHandler(); + h.destroyProcess(); + h.detachProcess(); + + if (!h.isStartNotified()) { + h.startNotify(); + } + } + } + + protected void disposeDebugProcess() throws InterruptedException { + if (myDebugProcess != null) { + + myDebugProcess.stop(); + + ProcessHandler h = myDebugProcess.getProcessHandler(); + if (!h.isProcessTerminated()) { + + h.destroyProcess(); + + if (!waitFor(h)) { + new Throwable("Cannot stop debugger process").printStackTrace(); + } + } + } + } +} diff --git a/python/testSrc/com/jetbrains/env/remote/PyRemoteDebuggerTest.java b/python/testSrc/com/jetbrains/env/remote/PyRemoteDebuggerTest.java new file mode 100644 index 000000000000..c76bb26e1e65 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/remote/PyRemoteDebuggerTest.java @@ -0,0 +1,85 @@ +package com.jetbrains.env.remote; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.util.ui.UIUtil; +import com.jetbrains.env.PyEnvTaskRunner; +import com.jetbrains.env.PyExecutionFixtureTestTask; +import com.jetbrains.env.PyTestTask; +import com.jetbrains.env.python.PythonDebuggerTest; +import com.jetbrains.python.remote.PyRemoteSdkAdditionalData; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author traff + */ +public class PyRemoteDebuggerTest extends PythonDebuggerTest { + @Override + protected void doRunTests(PyTestTask testTask, String testName, List roots) { + if (RUN_REMOTE && PyTestRemoteSdkProvider.shouldRunRemoteSdk(testTask)) { + if (PyTestRemoteSdkProvider.canRunRemoteSdk()) { + PyEnvTaskRunner remoteRunner = new RemoteTaskRunner(roots); + remoteRunner.runTask(testTask, testName); + } + else { + if (PyTestRemoteSdkProvider.shouldFailWhenCantRunRemote()) { + fail("Cant run with remote sdk: password isn't set"); + } + } + } + } + + private static class RemoteTaskRunner extends PyEnvTaskRunner { + private RemoteTaskRunner(List roots) { + super(roots); + } + + @Override + protected String getExecutable(String root, PyTestTask testTask) { + try { + final Sdk sdk = PyTestRemoteSdkProvider.getInstance().getSdk(getProject(testTask), super.getExecutable(root, testTask)); + + if (ProjectJdkTable.getInstance().findJdk(sdk.getName()) == null) { + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + ProjectJdkTable.getInstance().addJdk(sdk); + } + }); + } + }); + } + + return ((PyRemoteSdkAdditionalData)sdk.getSdkAdditionalData()).getRemoteSdkCredentials(false).getSdkId(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Nullable + public Project getProject(PyTestTask task) { + if (task instanceof PyExecutionFixtureTestTask) { + return ((PyExecutionFixtureTestTask)task).getProject(); + } + return null; + } + + @Override + protected boolean shouldRun(String root, PyTestTask task) { + return !isJython(super.getExecutable(root, task)); //TODO: make remote tests work for jython + } + + @Override + protected String getEnvType() { + return "remote"; + } + } +} diff --git a/python/testSrc/com/jetbrains/env/remote/PyTestRemoteSdkProvider.java b/python/testSrc/com/jetbrains/env/remote/PyTestRemoteSdkProvider.java new file mode 100644 index 000000000000..743082c9bfba --- /dev/null +++ b/python/testSrc/com/jetbrains/env/remote/PyTestRemoteSdkProvider.java @@ -0,0 +1,138 @@ +package com.jetbrains.env.remote; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Lists; +import com.google.common.hash.Hashing; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.remote.RemoteCredentialsHolder; +import com.intellij.remote.RemoteSdkException; +import com.intellij.testFramework.UsefulTestCase; +import com.intellij.util.ui.UIUtil; +import com.jetbrains.env.PyTestTask; +import com.jetbrains.python.remote.PyRemoteInterpreterManagerImpl; +import com.jetbrains.python.remote.PyRemoteSdkAdditionalData; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +/** + * @author traff + */ +public class PyTestRemoteSdkProvider { + public static final String SSH_PASSWORD = "SSH_PASSWORD"; + public static final String LOCAL_SSH_WITHOUT_PASSWORD = "LOCAL_SSH_WITHOUT_PASSWORD"; + private final PyRemoteInterpreterManagerImpl myInterpreterManager; + private static final String SSH_USER_NAME = "SSH_USER_NAME"; + + private Project myProject; + + private static PyTestRemoteSdkProvider INSTANCE = null; + + public synchronized static PyTestRemoteSdkProvider getInstance() { + if (INSTANCE == null) { + INSTANCE = new PyTestRemoteSdkProvider(); + } + return INSTANCE; + } + + public PyTestRemoteSdkProvider() { + myInterpreterManager = new PyRemoteInterpreterManagerImpl(); + } + + private final LoadingCache mySdkTable = CacheBuilder.newBuilder() + .build( + new CacheLoader() { + @Override + public Sdk load(String key) throws Exception { + return createSdk(key); + } + } + ); + + public Sdk getSdk(Project project, String sdkPath) throws ExecutionException { + myProject = project; + return mySdkTable.get(sdkPath); + } + + private Sdk createSdk(String interpreterPath) throws RemoteSdkException { + try { + PyRemoteSdkAdditionalData data = createRemoteSdkData(interpreterPath); + + final Sdk sdk = myInterpreterManager.createRemoteSdk(myProject, data, null, Lists.newArrayList()); + UIUtil.invokeAndWaitIfNeeded(new Runnable() { + @Override + public void run() { + myInterpreterManager.initSdk(sdk, myProject, null); + } + }); + + return sdk; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static RemoteCredentialsHolder getTestSdkCredentials() { + RemoteCredentialsHolder data = new RemoteCredentialsHolder(); + data.setHost("localhost"); + data.setPort(22); + data.setUserName(getUserName()); + data.setPassword(getPassword()); + return data; + } + + private static PyRemoteSdkAdditionalData createRemoteSdkData(final String path) throws IOException { + PyRemoteSdkAdditionalData data = new PyRemoteSdkAdditionalData(path); + data.setSshCredentials(getTestSdkCredentials()); + data.setInterpreterPath(path); + try { + data.setHelpersPath(getTempHelpersPath(path)); + } + catch (IOException e) { + throw new IllegalStateException(e); + } + return data; + } + + public static boolean canRunRemoteSdk() { + return isPasswordSpecified(); + } + + private static boolean isPasswordSpecified() { + return !StringUtil.isEmpty(System.getenv(LOCAL_SSH_WITHOUT_PASSWORD)) || !StringUtil.isEmpty(getPassword()); + } + + public static boolean shouldFailWhenCantRunRemote() { + return !StringUtil.isEmpty(System.getenv("FAIL_WHEN_CANT_RUN_REMOTE")) || UsefulTestCase.IS_UNDER_TEAMCITY; + } + + private static String getTempHelpersPath(@NotNull String interpreterPath) throws IOException { + final File dir = new File("/tmp"); + + File tmpDir = FileUtil.createTempDirectory(dir, "pycharm_helpers_", "_" + Math.abs( + Hashing.md5().hashUnencodedChars(interpreterPath).asInt()), true); + return tmpDir.getPath(); + } + + public static String getUserName() { + String userName = System.getenv(SSH_USER_NAME); + return StringUtil.isEmpty(userName) ? System.getProperty("user.name") : userName; + } + + public static String getPassword() { + return System.getenv(SSH_PASSWORD); + } + + public static boolean shouldRunRemoteSdk(PyTestTask task) { + return SystemInfo.isUnix; + } +} diff --git a/python/testSrc/com/jetbrains/env/ut/PyDocTestTask.java b/python/testSrc/com/jetbrains/env/ut/PyDocTestTask.java new file mode 100644 index 000000000000..e8228d767287 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/ut/PyDocTestTask.java @@ -0,0 +1,24 @@ +package com.jetbrains.env.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 index 000000000000..e130dd5717a4 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/ut/PyNoseTestTask.java @@ -0,0 +1,32 @@ +package com.jetbrains.env.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 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 index 000000000000..1e4261188206 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/ut/PyTestTestTask.java @@ -0,0 +1,40 @@ +package com.jetbrains.env.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 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 index 000000000000..2a2edc97a404 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/ut/PyUnitTestTask.java @@ -0,0 +1,240 @@ +package com.jetbrains.env.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.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.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 NOT_SUIT = new Filter() { + @Override + public boolean shouldAccept(SMTestProxy test) { + return !test.isSuite(); + } + }; +}