diff --git a/python/helpers/pycharm/_jb_tox_runner.py b/python/helpers/pycharm/_jb_tox_runner.py
new file mode 100644
index 000000000000..b1b12bee5b3b
--- /dev/null
+++ b/python/helpers/pycharm/_jb_tox_runner.py
@@ -0,0 +1,68 @@
+# coding=utf-8
+"""
+Runs tox from current directory.
+It supports any runner, but well-known runners (py.test and unittest) are switched to our internal runners to provide better support
+"""
+import sys
+from tox import config as tox_config, session as tox_session
+from tox.session import Reporter
+
+from tcmessages import TeamcityServiceMessages
+import os
+
+helpers_dir = str(os.path.split(__file__)[0])
+
+# List of local runners to use
+_RUNNERS = {"unit2": [os.path.join(helpers_dir, "utrunner.py"), os.getcwd(), "true"]}
+
+teamcity = TeamcityServiceMessages()
+
+
+
+class _Reporter(Reporter):
+ def logaction_start(self, action):
+ super(_Reporter, self).logaction_start(action)
+ if action.activity == "getenv":
+ teamcity.testSuiteStarted(action.id)
+ self.current_suite = action.id
+
+ def logaction_finish(self, action):
+ super(_Reporter, self).logaction_finish(action)
+ if action.activity == "runtests":
+ teamcity.testSuiteFinished(action.id)
+
+
+ def error(self, msg):
+ super(_Reporter, self).error(msg)
+ name = teamcity.current_test_name()
+ if name:
+ teamcity.testError(name, msg)
+ if name == teamcity.topmost_suite:
+ teamcity.testSuiteFinished(name)
+ else:
+ sys.stderr.write(msg)
+
+ def skip(self, msg):
+ super(_Reporter, self).skip(msg)
+ name = teamcity.current_test_name()
+ if name:
+ teamcity.testFinished(name)
+
+
+
+
+config = tox_config.parseconfig()
+for env, tmp_config in config.envconfigs.items():
+ if not tmp_config.setenv:
+ tmp_config.setenv = dict()
+ tmp_config.setenv.update({"_jb_do_not_call_enter_matrix": "1"})
+ commands = tmp_config.commands
+ if isinstance(commands, list) and len(commands) == 1:
+ command_with_arguments = commands[0]
+ if command_with_arguments[0] in _RUNNERS:
+ command_with_arguments = _RUNNERS[command_with_arguments[0]]
+ tmp_config.commands = [command_with_arguments]
+
+session = tox_session.Session(config, Report=_Reporter)
+teamcity.testMatrixEntered()
+session.runcommand()
\ No newline at end of file
diff --git a/python/helpers/pycharm/tcmessages.py b/python/helpers/pycharm/tcmessages.py
index a567ed5438e5..d9601da2129c 100644
--- a/python/helpers/pycharm/tcmessages.py
+++ b/python/helpers/pycharm/tcmessages.py
@@ -7,6 +7,14 @@ class TeamcityServiceMessages:
def __init__(self, output=sys.stdout, prepend_linebreak=False):
self.output = output
self.prepend_linebreak = prepend_linebreak
+ self.test_stack = []
+ """
+ Names of tests
+ """
+ self.topmost_suite = None
+ """
+ Last suite we entered in
+ """
def escapeValue(self, value):
if sys.version_info[0] <= 2 and isinstance(value, unicode):
@@ -28,21 +36,27 @@ class TeamcityServiceMessages:
def testSuiteStarted(self, suiteName, location=None):
self.message('testSuiteStarted', name=suiteName, locationHint=location)
+ self.test_stack.append(suiteName)
+ self.topmost_suite = suiteName
def testSuiteFinished(self, suiteName):
self.message('testSuiteFinished', name=suiteName)
+ self.__pop_current_test()
def testStarted(self, testName, location=None):
self.message('testStarted', name=testName, locationHint=location)
+ self.test_stack.append(testName)
+
def testFinished(self, testName, duration=None):
self.message('testFinished', name=testName, duration=duration)
+ self.__pop_current_test()
+
def testIgnored(self, testName, message=''):
self.message('testIgnored', name=testName, message=message)
self.testFinished(testName)
-
def testFailed(self, testName, message='', details='', expected='', actual='', duration=None):
"""
Marks test as failed. *CAUTION*: This method calls ``testFinished``, so you do not need
@@ -56,10 +70,24 @@ class TeamcityServiceMessages:
self.message('testFailed', name=testName, message=message, details=details)
self.testFinished(testName, int(duration) if duration else None)
+
+ def __pop_current_test(self):
+ try:
+ self.test_stack.pop()
+ except IndexError:
+ pass
+
def testError(self, testName, message='', details='', duration=None):
self.message('testFailed', name=testName, message=message, details=details, error="true")
self.testFinished(testName, int(duration) if duration else None)
+
+ def current_test_name(self):
+ """
+ :return: name of current test we are in
+ """
+ return self.test_stack[-1] if len(self.test_stack) > 0 else None
+
def testStdOut(self, testName, out):
self.message('testStdOut', name=testName, out=out)
diff --git a/python/helpers/pycharm/tcunittest.py b/python/helpers/pycharm/tcunittest.py
index b688d804c653..185c12898a1e 100644
--- a/python/helpers/pycharm/tcunittest.py
+++ b/python/helpers/pycharm/tcunittest.py
@@ -1,3 +1,4 @@
+import os
import traceback, sys
from unittest import TestResult
import datetime
@@ -37,13 +38,18 @@ def smart_str(s):
class TeamcityTestResult(TestResult):
+ """
+ Set ``_jb_do_not_call_enter_matrix`` to prevent it from runnig "enter matrix"
+ """
+
def __init__(self, stream=sys.stdout, *args, **kwargs):
TestResult.__init__(self)
for arg, value in kwargs.items():
setattr(self, arg, value)
self.output = stream
self.messages = TeamcityServiceMessages(self.output, prepend_linebreak=True)
- self.messages.testMatrixEntered()
+ if not "_jb_do_not_call_enter_matrix" in os.environ:
+ self.messages.testMatrixEntered()
self.current_failed = False
self.current_suite = None
self.subtest_suite = None
diff --git a/python/helpers/pycharm/utrunner.py b/python/helpers/pycharm/utrunner.py
index bd04fedcff53..dfaaad58e12a 100644
--- a/python/helpers/pycharm/utrunner.py
+++ b/python/helpers/pycharm/utrunner.py
@@ -38,6 +38,8 @@ def loadSource(fileName):
cnt += 1
moduleName = getModuleName(prefix, cnt)
debug("/ Loading " + fileName + " as " + moduleName)
+ if os.path.isdir(fileName):
+ fileName = fileName + os.path.sep
module = imp.load_source(moduleName, fileName)
modules[moduleName] = module
return module
@@ -56,17 +58,15 @@ def walkModules(modulesAndPattern, dirname, names):
# For default pattern see https://docs.python.org/2/library/unittest.html#test-discovery
def loadModulesFromFolderRec(folder, pattern="test*.py"):
modules = []
- if PYTHON_VERSION_MAJOR == 3:
- # fnmatch converts glob to regexp
- prog_list = [re.compile(fnmatch.translate(pat.strip())) for pat in pattern.split(',')]
- for root, dirs, files in os.walk(folder):
- for name in files:
- for prog in prog_list:
- if name.endswith(".py") and prog.match(name):
- modules.append(loadSource(os.path.join(root, name)))
- else: # actually for jython compatibility
- os.path.walk(folder, walkModules, (modules, pattern))
-
+ # fnmatch converts glob to regexp
+ prog_list = [re.compile(fnmatch.translate(pat.strip())) for pat in pattern.split(',')]
+ for root, dirs, files in os.walk(folder):
+ files = [f for f in files if not f[0] == '.']
+ dirs[:] = [d for d in dirs if not d[0] == '.']
+ for name in files:
+ for prog in prog_list:
+ if name.endswith(".py") and prog.match(name):
+ modules.append(loadSource(os.path.join(root, name)))
return modules
testLoader = TestLoader()
@@ -109,11 +109,11 @@ if __name__ == "__main__":
a_splitted = a[0].split("_args_separator_") # ";" can't be used with bash, so we use "_args_separator_"
if len(a_splitted) != 1:
# means we have pattern to match against
- if a_splitted[0].endswith(os.path.sep):
+ if os.path.isdir(a_splitted[0]):
debug("/ from folder " + a_splitted[0] + ". Use pattern: " + a_splitted[1])
modules = loadModulesFromFolderRec(a_splitted[0], a_splitted[1])
else:
- if a[0].endswith(os.path.sep):
+ if os.path.isdir(a[0]):
debug("/ from folder " + a[0])
modules = loadModulesFromFolderRec(a[0])
else:
diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml
index 952fc1fe3e9c..12ef08852ae2 100644
--- a/python/src/META-INF/python-core.xml
+++ b/python/src/META-INF/python-core.xml
@@ -137,10 +137,12 @@
+
+
diff --git a/python/src/com/jetbrains/python/PythonHelper.java b/python/src/com/jetbrains/python/PythonHelper.java
index 3816e5ba9f04..1eee40c98919 100644
--- a/python/src/com/jetbrains/python/PythonHelper.java
+++ b/python/src/com/jetbrains/python/PythonHelper.java
@@ -50,6 +50,7 @@ public enum PythonHelper implements HelperPackage {
// Test runners
UT("pycharm", "utrunner"),
+ TOX("pycharm", "_jb_tox_runner"),
SETUPPY("pycharm", "pycharm_setup_runner"),
NOSE("pycharm", "noserunner"),
PYTEST("pycharm", "pytestrunner"),
diff --git a/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java b/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java
index 5beca8a17bae..8dea7e5b2479 100644
--- a/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java
+++ b/python/src/com/jetbrains/python/testing/PyRerunFailedTestsAction.java
@@ -104,6 +104,7 @@ public class PyRerunFailedTestsAction extends AbstractRerunFailedTestsAction {
return myState.getRunner();
}
+ @NotNull
@Override
protected List getTestSpecs() {
List specs = new ArrayList();
diff --git a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
index b702b94b77d6..103f2a0856ec 100644
--- a/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
+++ b/python/src/com/jetbrains/python/testing/PythonTestCommandLineStateBase.java
@@ -173,5 +173,6 @@ public abstract class PythonTestCommandLineStateBase extends PythonCommandLineSt
}
protected abstract HelperPackage getRunner();
+ @NotNull
protected abstract List getTestSpecs();
}
diff --git a/python/src/com/jetbrains/python/testing/PythonTestConfigurationProducer.java b/python/src/com/jetbrains/python/testing/PythonTestConfigurationProducer.java
index 10f7e018d1fc..21ab8076ab84 100644
--- a/python/src/com/jetbrains/python/testing/PythonTestConfigurationProducer.java
+++ b/python/src/com/jetbrains/python/testing/PythonTestConfigurationProducer.java
@@ -17,6 +17,7 @@ package com.jetbrains.python.testing;
import com.google.common.collect.Sets;
import com.intellij.execution.Location;
+import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.actions.ConfigurationFromContext;
import com.intellij.execution.actions.RunConfigurationProducer;
@@ -58,7 +59,7 @@ abstract public class PythonTestConfigurationProducer extends RunConfigurationPr
super(configurationFactory);
}
- @Override
+ @Override
public boolean isConfigurationFromContext(AbstractPythonTestRunConfiguration configuration, ConfigurationContext context) {
final Location location = context.getLocation();
if (location == null || !isAvailable(location)) return false;
diff --git a/python/src/com/jetbrains/python/testing/attest/PythonAtTestCommandLineState.java b/python/src/com/jetbrains/python/testing/attest/PythonAtTestCommandLineState.java
index cfe65d3067a1..c0c0a93e8485 100644
--- a/python/src/com/jetbrains/python/testing/attest/PythonAtTestCommandLineState.java
+++ b/python/src/com/jetbrains/python/testing/attest/PythonAtTestCommandLineState.java
@@ -19,6 +19,7 @@ import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.util.io.FileUtil;
import com.jetbrains.python.PythonHelper;
import com.jetbrains.python.testing.PythonTestCommandLineStateBase;
+import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -40,6 +41,7 @@ public class PythonAtTestCommandLineState extends PythonTestCommandLineStateBase
return PythonHelper.ATTEST;
}
+ @NotNull
protected List getTestSpecs() {
List specs = new ArrayList();
diff --git a/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java b/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java
index a47242aad107..e12e93d3edab 100644
--- a/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java
+++ b/python/src/com/jetbrains/python/testing/doctest/PythonDocTestCommandLineState.java
@@ -18,6 +18,7 @@ package com.jetbrains.python.testing.doctest;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.jetbrains.python.PythonHelper;
import com.jetbrains.python.testing.PythonTestCommandLineStateBase;
+import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -39,6 +40,7 @@ public class PythonDocTestCommandLineState extends PythonTestCommandLineStateBas
return PythonHelper.DOCSTRING;
}
+ @NotNull
protected List getTestSpecs() {
List specs = new ArrayList();
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestCommandLineState.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestCommandLineState.java
index 4d9393b8cedd..0dc5647eac16 100644
--- a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestCommandLineState.java
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestCommandLineState.java
@@ -22,6 +22,7 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.jetbrains.python.PythonHelper;
import com.jetbrains.python.testing.PythonTestCommandLineStateBase;
+import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -42,6 +43,7 @@ public class PythonNoseTestCommandLineState extends PythonTestCommandLineStateBa
return PythonHelper.NOSE;
}
+ @NotNull
protected List getTestSpecs() {
List specs = new ArrayList();
diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java b/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java
index dd202187e83c..d44a93508554 100644
--- a/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java
+++ b/python/src/com/jetbrains/python/testing/pytest/PyTestCommandLineState.java
@@ -55,6 +55,7 @@ public class PyTestCommandLineState extends PythonTestCommandLineStateBase {
return PythonHelper.PYTEST;
}
+ @NotNull
@Override
protected List getTestSpecs() {
List specs = new ArrayList();
diff --git a/python/src/com/jetbrains/python/testing/tox/PyToxCommandLineState.java b/python/src/com/jetbrains/python/testing/tox/PyToxCommandLineState.java
new file mode 100644
index 000000000000..5025ce19fe11
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/tox/PyToxCommandLineState.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.testing.tox;
+
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.jetbrains.python.HelperPackage;
+import com.jetbrains.python.PythonHelper;
+import com.jetbrains.python.testing.PythonTestCommandLineStateBase;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Ilya.Kazakevich
+ */
+class PyToxCommandLineState extends PythonTestCommandLineStateBase {
+
+ public PyToxCommandLineState(PyToxConfiguration configuration,
+ ExecutionEnvironment environment) {
+ super(configuration, environment);
+ }
+
+ @Override
+ protected HelperPackage getRunner() {
+ return PythonHelper.TOX;
+ }
+
+ @NotNull
+ @Override
+ protected List getTestSpecs() {
+ return Collections.emptyList();
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/tox/PyToxConfiguration.java b/python/src/com/jetbrains/python/testing/tox/PyToxConfiguration.java
new file mode 100644
index 000000000000..04568c2069d8
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/tox/PyToxConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.testing.tox;
+
+import com.intellij.execution.Executor;
+import com.intellij.execution.configurations.RunProfileState;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.python.run.AbstractPythonRunConfiguration;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Ilya.Kazakevich
+ */
+final class PyToxConfiguration extends AbstractPythonRunConfiguration {
+
+ @NotNull
+ private final Project myProject;
+
+ PyToxConfiguration(@NotNull final PyToxConfigurationFactory factory, @NotNull final Project project) {
+ super(project, factory);
+ myProject = project;
+ }
+
+
+ @Override
+ protected SettingsEditor createConfigurationEditor() {
+ return new PyToxConfigurationSettings(myProject);
+ }
+
+ @Nullable
+ @Override
+ public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment environment) {
+ getWorkingDirectory();
+ return new PyToxCommandLineState(this, environment);
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationFactory.java b/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationFactory.java
new file mode 100644
index 000000000000..faaae776ee47
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.testing.tox;
+
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.ConfigurationType;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.python.run.PythonConfigurationFactoryBase;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Ilya.Kazakevich
+ */
+public final class PyToxConfigurationFactory extends PythonConfigurationFactoryBase {
+ public static final ConfigurationFactory INSTANCE = new PyToxConfigurationFactory(PyToxConfigurationType.INSTANCE);
+
+ public PyToxConfigurationFactory(@NotNull final ConfigurationType type) {
+ super(type);
+ }
+
+ @Override
+ public String getName() {
+ return "Tox";
+ }
+
+ @NotNull
+ @Override
+ public RunConfiguration createTemplateConfiguration(@NotNull final Project project) {
+ return new PyToxConfiguration(this, project);
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationProducer.java b/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationProducer.java
new file mode 100644
index 000000000000..a80cbe2a51b3
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationProducer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.testing.tox;
+
+import com.intellij.execution.actions.ConfigurationContext;
+import com.intellij.execution.actions.RunConfigurationProducer;
+import com.intellij.openapi.util.Ref;
+import com.intellij.psi.PsiElement;
+
+/**
+ * @author Ilya.Kazakevich
+ */
+public final class PyToxConfigurationProducer extends RunConfigurationProducer {
+
+ public PyToxConfigurationProducer() {
+ super(PyToxConfigurationFactory.INSTANCE);
+ }
+
+ @Override
+ public boolean isConfigurationFromContext(PyToxConfiguration configuration, ConfigurationContext context) {
+ return false;
+ }
+
+ @Override
+ protected boolean setupConfigurationFromContext(PyToxConfiguration configuration,
+ ConfigurationContext context,
+ Ref sourceElement) {
+ return false;
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationSettings.java b/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationSettings.java
new file mode 100644
index 000000000000..1b5691470144
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationSettings.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.testing.tox;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SettingsEditor;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.python.run.AbstractPyCommonOptionsForm;
+import com.jetbrains.python.run.AbstractPythonRunConfiguration;
+import com.jetbrains.python.run.PyCommonOptionsFormData;
+import com.jetbrains.python.run.PyCommonOptionsFormFactory;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.List;
+
+/**
+ * @author Ilya.Kazakevich
+ */
+final class PyToxConfigurationSettings extends SettingsEditor {
+ @NotNull
+ private final Project myProject;
+ private AbstractPyCommonOptionsForm myForm;
+
+ PyToxConfigurationSettings(@NotNull final Project project) {
+ myProject = project;
+ }
+
+ @Override
+ protected void applyEditorTo(final PyToxConfiguration s) throws ConfigurationException {
+ AbstractPythonRunConfiguration.copyParams(myForm, s);
+ }
+
+ @Override
+ protected void resetEditorFrom(final PyToxConfiguration s) {
+ AbstractPythonRunConfiguration.copyParams(s, myForm);
+ }
+
+ @NotNull
+ @Override
+ protected JComponent createEditor() {
+
+ final JPanel panel = new JPanel(new BorderLayout());
+ panel.add(new JLabel("Tox"), BorderLayout.PAGE_START);
+
+ myForm = createEnvPanel();
+ final JComponent envPanel = myForm.getMainPanel();
+
+ panel.add(envPanel, BorderLayout.PAGE_END);
+
+ return panel;
+ }
+
+ @NotNull
+ private AbstractPyCommonOptionsForm createEnvPanel() {
+ return PyCommonOptionsFormFactory.getInstance().createForm(new PyCommonOptionsFormData() {
+ @Override
+ public Project getProject() {
+ return myProject;
+ }
+
+ @Override
+ public List getValidModules() {
+ return AbstractPythonRunConfiguration.getValidModules(myProject);
+ }
+
+ @Override
+ public boolean showConfigureInterpretersLink() {
+ return false;
+ }
+ });
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationType.java b/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationType.java
new file mode 100644
index 000000000000..afe9ef3b97aa
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/tox/PyToxConfigurationType.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jetbrains.python.testing.tox;
+
+import com.intellij.execution.configurations.ConfigurationFactory;
+import com.intellij.execution.configurations.ConfigurationType;
+import icons.PythonIcons;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * @author Ilya.Kazakevich
+ */
+public class PyToxConfigurationType implements ConfigurationType {
+
+ public static final String ID = "tox";
+ public static final ConfigurationType INSTANCE = new PyToxConfigurationType();
+
+ @Override
+ public ConfigurationFactory[] getConfigurationFactories() {
+ return new ConfigurationFactory[]{new PyToxConfigurationFactory(this)};
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "tox";
+ }
+
+ @Override
+ public String getConfigurationTypeDescription() {
+ return "Tox runner";
+ }
+
+ @Override
+ public Icon getIcon() {
+ return PythonIcons.Python.PythonTests;
+ }
+
+ @NotNull
+ @Override
+ public String getId() {
+ return ID;
+ }
+}
diff --git a/python/src/com/jetbrains/python/testing/tox/package-info.java b/python/src/com/jetbrains/python/testing/tox/package-info.java
new file mode 100644
index 000000000000..f6ca14dbfe6e
--- /dev/null
+++ b/python/src/com/jetbrains/python/testing/tox/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2000-2015 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * tox test runner
+ * @author Ilya.Kazakevich
+ */
+package com.jetbrains.python.testing.tox;
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/testing/unittest/PythonUnitTestCommandLineState.java b/python/src/com/jetbrains/python/testing/unittest/PythonUnitTestCommandLineState.java
index 8ecc4d60a7d0..1915371a6f6a 100644
--- a/python/src/com/jetbrains/python/testing/unittest/PythonUnitTestCommandLineState.java
+++ b/python/src/com/jetbrains/python/testing/unittest/PythonUnitTestCommandLineState.java
@@ -24,6 +24,7 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonHelper;
import com.jetbrains.python.testing.AbstractPythonTestRunConfiguration;
import com.jetbrains.python.testing.PythonTestCommandLineStateBase;
+import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -49,6 +50,7 @@ public class PythonUnitTestCommandLineState extends
return PythonHelper.UT;
}
+ @NotNull
protected List getTestSpecs() {
List specs = new ArrayList();