diff --git a/python/helpers/pycharm/_jb_runner_tools.py b/python/helpers/pycharm/_jb_runner_tools.py index b17c44ba6277..ceec9f695c7a 100644 --- a/python/helpers/pycharm/_jb_runner_tools.py +++ b/python/helpers/pycharm/_jb_runner_tools.py @@ -309,19 +309,42 @@ class _SymbolNameSplitter(object): class _SymbolName2KSplitter(_SymbolNameSplitter): """ - Based on imp which works in 2, but not 3 + Based on imp which works in 2, but not 3. + It also emulates packages for folders with out of __init__.py. + Say, you have Python path "spam.eggs" where "spam" is plain folder. + It works for Py3, but not Py2. + find_module for "spam" raises exception which is processed then (see "_symbol_processed") """ def __init__(self): super(_SymbolNameSplitter, self).__init__() self._path = None + # Set to True when at least one find_module success, so we have at least one symbol + self._symbol_processed = False def check_is_importable(self, parts, current_step, separator): import imp module_to_import = parts[current_step] - (fil, self._path, desc) = imp.find_module(module_to_import, [self._path] if self._path else None) - if desc[2] == imp.PKG_DIRECTORY: - # Package - self._path = imp.load_module(module_to_import, fil, self._path, desc).__path__[0] + try: + (fil, self._path, desc) = imp.find_module(module_to_import, [self._path] if self._path else None) + self._symbol_processed = True + if desc[2] == imp.PKG_DIRECTORY: + # Package + self._path = imp.load_module(module_to_import, fil, self._path, desc).__path__[0] + except ImportError as error: + if not self._symbol_processed: + # First ImportError means there could be folder with out for __init__.py + # See class doc for more info + subdir = os.path.sep.join(parts[:current_step + 1]) + dirs = [path for path in map( lambda p: os.path.join(p, subdir), sys.path) if os.path.isdir(path)] + if not dirs: + raise error + elif len(dirs) == 1: + # can be folder with out of __init__.py + self._path = dirs[0] + return + else: + raise Exception("Several folders on sys.path with same name, rename folder: {0}", ",".join(dirs)) + raise error class _SymbolName3KSplitter(_SymbolNameSplitter): diff --git a/python/testData/testRunner/env/pytest/folder_no_init_py/test_test.py b/python/testData/testRunner/env/pytest/folder_no_init_py/test_test.py new file mode 100644 index 000000000000..fb92113cf97b --- /dev/null +++ b/python/testData/testRunner/env/pytest/folder_no_init_py/test_test.py @@ -0,0 +1,8 @@ + + +def test_test(): + assert False + + +def test_2_test(): + pass diff --git a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java index 676f3fc96119..3bce18627584 100644 --- a/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java +++ b/python/testSrc/com/jetbrains/env/python/testing/PythonPyTestingTest.java @@ -29,7 +29,7 @@ import static org.junit.Assert.assertEquals; * User : catherine */ @EnvTestTagsRequired(tags = "pytest") -public class PythonPyTestingTest extends PyEnvTestCase { +public final class PythonPyTestingTest extends PyEnvTestCase { @Test public void testConfigurationProducer() throws Exception { @@ -91,13 +91,14 @@ public class PythonPyTestingTest extends PyEnvTestCase { @Test public void testProduceConfigurationOnFile() throws Exception { - runPythonTest(new CreateConfigurationTestTask(PythonTestConfigurationsModel.PY_TEST_NAME, PyUniversalPyTestConfiguration.class, "spam.py"){ - @NotNull - @Override - protected PsiElement getElementToRightClickOnByFile(@NotNull final String fileName) { - return myFixture.configureByFile(fileName); - } - }); + runPythonTest( + new CreateConfigurationTestTask(PythonTestConfigurationsModel.PY_TEST_NAME, PyUniversalPyTestConfiguration.class, "spam.py") { + @NotNull + @Override + protected PsiElement getElementToRightClickOnByFile(@NotNull final String fileName) { + return myFixture.configureByFile(fileName); + } + }); } @Test @@ -215,6 +216,36 @@ public class PythonPyTestingTest extends PyEnvTestCase { }); } + /** + * Ensure we can run path like "spam.bar" where "spam" is folder with out of init.py + */ + @Test + public void testPyTestFolderNoInitPy() { + runPythonTest(new PyProcessWithConsoleTestTask("/testRunner/env/pytest", SdkCreationType.EMPTY_SDK) { + @NotNull + @Override + protected PyTestTestProcessRunner createProcessRunner() throws Exception { + return new PyTestTestProcessRunner("folder_no_init_py/test_test.py", 2); + } + + @Override + protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner, + @NotNull final String stdout, + @NotNull final String stderr, + @NotNull final String all) { + assertEquals(runner.getFormattedTestTree(), 1, runner.getFailedTestsCount()); + if (runner.getCurrentRerunStep() == 0) { + assertEquals(runner.getFormattedTestTree(), 2, runner.getAllTestsCount()); + assertEquals(runner.getFormattedTestTree(), 1, runner.getPassedTestsCount()); + } + else { + assertEquals(runner.getFormattedTestTree(), 1, runner.getAllTestsCount()); + assertEquals(runner.getFormattedTestTree(), 0, runner.getPassedTestsCount()); + } + } + }); + } + @Test public void testPytestRunner2() { runPythonTest(new PyProcessWithConsoleTestTask("/testRunner/env/pytest", SdkCreationType.EMPTY_SDK) {