summaryrefslogtreecommitdiff
path: root/tox
diff options
context:
space:
mode:
Diffstat (limited to 'tox')
-rw-r--r--tox/__init__.py8
-rw-r--r--tox/_pytestplugin.py9
-rw-r--r--tox/_quickstart.py2
-rw-r--r--tox/config.py50
-rw-r--r--tox/interpreters.py6
-rw-r--r--tox/result.py6
-rw-r--r--tox/session.py34
7 files changed, 81 insertions, 34 deletions
diff --git a/tox/__init__.py b/tox/__init__.py
index c17341d..baf651b 100644
--- a/tox/__init__.py
+++ b/tox/__init__.py
@@ -1,5 +1,5 @@
#
-__version__ = '2.3.2'
+__version__ = '2.4.0.dev1'
from .hookspecs import hookspec, hookimpl # noqa
@@ -23,5 +23,11 @@ class exception:
""" a directory did not exist. """
class MissingDependency(Error):
""" a dependency could not be found or determined. """
+ class MinVersionError(Error):
+ """ the installed tox version is lower than requested minversion. """
+
+ def __init__(self, message):
+ self.message = message
+ super(exception.MinVersionError, self).__init__(message)
from tox.session import main as cmdline # noqa
diff --git a/tox/_pytestplugin.py b/tox/_pytestplugin.py
index f15d2ec..785070d 100644
--- a/tox/_pytestplugin.py
+++ b/tox/_pytestplugin.py
@@ -68,12 +68,12 @@ class ReportExpectMock:
def generic_report(*args, **kwargs):
self._calls.append((name,) + args)
- print ("%s" % (self._calls[-1], ))
+ print("%s" % (self._calls[-1], ))
return generic_report
def action(self, venv, msg, *args):
self._calls.append(("action", venv, msg))
- print ("%s" % (self._calls[-1], ))
+ print("%s" % (self._calls[-1], ))
return Action(self.session, venv, msg, args)
def getnext(self, cat):
@@ -195,6 +195,9 @@ class Cmd:
return py.std.subprocess.Popen(argv, stdout=stdout, stderr=stderr, **kw)
def run(self, *argv):
+ if argv[0] == "tox" and sys.version_info[:2] < (2, 7):
+ pytest.skip("can not run tests involving calling tox on python2.6. "
+ "(and python2.6 is about to be deprecated anyway)")
argv = [str(x) for x in argv]
assert py.path.local.sysfind(str(argv[0])), argv[0]
p1 = self.tmpdir.join("stdout")
@@ -316,7 +319,7 @@ def initproj(request, tmpdir):
for p in base.visit(lambda x: x.check(file=1)):
manifestlines.append("include %s" % p.relto(base))
create_files(base, {"MANIFEST.in": "\n".join(manifestlines)})
- print ("created project in %s" % (base,))
+ print("created project in %s" % (base,))
base.chdir()
return initproj
diff --git a/tox/_quickstart.py b/tox/_quickstart.py
index a397cb1..59ee48e 100644
--- a/tox/_quickstart.py
+++ b/tox/_quickstart.py
@@ -119,7 +119,7 @@ def do_prompt(d, key, text, default=None, validator=nonempty):
x = term_input(prompt)
if default and not x:
x = default
- if sys.version_info < (3, ) and not isinstance(x, unicode):
+ if sys.version_info < (3, ) and not isinstance(x, unicode): # noqa
# for Python 2.x, try to get a Unicode string out of it
if x.decode('ascii', 'replace').encode('ascii', 'replace') != x:
if TERM_ENCODING:
diff --git a/tox/config.py b/tox/config.py
index 974bbbb..99e678d 100644
--- a/tox/config.py
+++ b/tox/config.py
@@ -12,6 +12,7 @@ import pluggy
import tox.interpreters
from tox import hookspecs
+from tox._verlib import NormalizedVersion
import py
@@ -181,7 +182,7 @@ class PosargsOption:
class InstallcmdOption:
name = "install_command"
type = "argv"
- default = "pip install {opts} {packages}"
+ default = "python -m pip install {opts} {packages}"
help = "install command for dependencies and package under test."
def postprocess(self, testenv_config, value):
@@ -225,7 +226,10 @@ def parseconfig(args=None, plugins=()):
if inipath.check():
break
else:
- feedback("toxini file %r not found" % (basename), sysexit=True)
+ inipath = py.path.local().join('setup.cfg')
+ if not inipath.check():
+ feedback("toxini file %r not found" % (basename), sysexit=True)
+
try:
parseini(config, inipath)
except tox.exception.InterpreterNotFound:
@@ -367,6 +371,9 @@ def tox_addoption(parser):
help="override sitepackages setting to True in all envs")
parser.add_argument("--skip-missing-interpreters", action="store_true",
help="don't fail tests for missing interpreters")
+ parser.add_argument("--workdir", action="store",
+ dest="workdir", metavar="PATH", default=None,
+ help="tox working directory")
parser.add_argument("args", nargs="*",
help="additional arguments available to command positional substitution")
@@ -518,6 +525,13 @@ def tox_addoption(parser):
help="install package in develop/editable mode")
parser.add_testenv_attribute_obj(InstallcmdOption())
+
+ parser.add_testenv_attribute(
+ name="list_dependencies_command",
+ type="argv",
+ default="python -m pip freeze",
+ help="list dependencies for a virtual environment")
+
parser.add_testenv_attribute_obj(DepOption())
parser.add_testenv_attribute(
@@ -645,12 +659,18 @@ class parseini:
self._cfg = py.iniconfig.IniConfig(config.toxinipath)
config._cfg = self._cfg
self.config = config
+
+ if inipath.basename == 'setup.cfg':
+ prefix = 'tox'
+ else:
+ prefix = None
ctxname = getcontextname()
if ctxname == "jenkins":
- reader = SectionReader("tox:jenkins", self._cfg, fallbacksections=['tox'])
+ reader = SectionReader("tox:jenkins", self._cfg, prefix=prefix,
+ fallbacksections=['tox'])
distshare_default = "{toxworkdir}/distshare"
elif not ctxname:
- reader = SectionReader("tox", self._cfg)
+ reader = SectionReader("tox", self._cfg, prefix=prefix)
distshare_default = "{homedir}/.tox/distshare"
else:
raise ValueError("invalid context")
@@ -665,8 +685,20 @@ class parseini:
reader.addsubstitutions(toxinidir=config.toxinidir,
homedir=config.homedir)
- config.toxworkdir = reader.getpath("toxworkdir", "{toxinidir}/.tox")
+ # As older versions of tox may have bugs or incompatabilities that
+ # prevent parsing of tox.ini this must be the first thing checked.
config.minversion = reader.getstring("minversion", None)
+ if config.minversion:
+ minversion = NormalizedVersion(self.config.minversion)
+ toxversion = NormalizedVersion(tox.__version__)
+ if toxversion < minversion:
+ raise tox.exception.MinVersionError(
+ "tox version is %s, required is at least %s" % (
+ toxversion, minversion))
+ if config.option.workdir is None:
+ config.toxworkdir = reader.getpath("toxworkdir", "{toxinidir}/.tox")
+ else:
+ config.toxworkdir = config.toxinidir.join(config.option.workdir, abs=True)
if not config.option.skip_missing_interpreters:
config.option.skip_missing_interpreters = \
@@ -854,8 +886,12 @@ is_section_substitution = re.compile("{\[[^{}\s]+\]\S+?}").match
class SectionReader:
- def __init__(self, section_name, cfgparser, fallbacksections=None, factors=()):
- self.section_name = section_name
+ def __init__(self, section_name, cfgparser, fallbacksections=None,
+ factors=(), prefix=None):
+ if prefix is None:
+ self.section_name = section_name
+ else:
+ self.section_name = "%s:%s" % (prefix, section_name)
self._cfg = cfgparser
self.fallbacksections = fallbacksections or []
self.factors = factors
diff --git a/tox/interpreters.py b/tox/interpreters.py
index 66e6409..e049f1d 100644
--- a/tox/interpreters.py
+++ b/tox/interpreters.py
@@ -43,10 +43,10 @@ class Interpreters:
try:
res = exec_on_interpreter(info.executable,
[inspect.getsource(sitepackagesdir),
- "print (sitepackagesdir(%r))" % envdir])
+ "print(sitepackagesdir(%r))" % envdir])
except ExecFailed:
val = sys.exc_info()[1]
- print ("execution failed: %s -- %s" % (val.out, val.err))
+ print("execution failed: %s -- %s" % (val.out, val.err))
return ""
else:
return res["dir"]
@@ -56,7 +56,7 @@ def run_and_get_interpreter_info(name, executable):
assert executable
try:
result = exec_on_interpreter(executable,
- [inspect.getsource(pyinfo), "print (pyinfo())"])
+ [inspect.getsource(pyinfo), "print(pyinfo())"])
except ExecFailed:
val = sys.exc_info()[1]
return NoInterpreterInfo(name, executable=val.executable,
diff --git a/tox/result.py b/tox/result.py
index 96ac612..bad8cc3 100644
--- a/tox/result.py
+++ b/tox/result.py
@@ -47,9 +47,9 @@ class EnvLog:
pythonexecutable = py.path.local(pythonexecutable)
out = pythonexecutable.sysexec("-c",
"import sys; "
- "print (sys.executable);"
- "print (list(sys.version_info)); "
- "print (sys.version)")
+ "print(sys.executable);"
+ "print(list(sys.version_info)); "
+ "print(sys.version)")
lines = out.splitlines()
executable = lines.pop(0)
version_info = eval(lines.pop(0))
diff --git a/tox/session.py b/tox/session.py
index 1c9bbef..e29696e 100644
--- a/tox/session.py
+++ b/tox/session.py
@@ -15,7 +15,6 @@ from tox._verlib import NormalizedVersion, IrrationalVersionError
from tox.venv import VirtualEnv
from tox.config import parseconfig
from tox.result import ResultLog
-from tox.hookspecs import hookimpl
from subprocess import STDOUT
@@ -41,6 +40,10 @@ def main(args=None):
raise SystemExit(retcode)
except KeyboardInterrupt:
raise SystemExit(2)
+ except tox.exception.MinVersionError as e:
+ r = Reporter(None)
+ r.error(e.message)
+ raise SystemExit(1)
def show_help(config):
@@ -234,6 +237,12 @@ class Reporter(object):
self._reportedlines = []
# self.cumulated_time = 0.0
+ def _get_verbosity(self):
+ if self.session:
+ return self.session.config.option.verbosity
+ else:
+ return 2
+
def logpopen(self, popen, env):
""" log information about the action.popen() created process. """
cmd = " ".join(map(str, popen.args))
@@ -253,16 +262,17 @@ class Reporter(object):
# self.cumulated_time += duration
self.verbosity2("%s finish: %s after %.2f seconds" % (
action.venvname, action.msg, duration), bold=True)
+ delattr(action, '_starttime')
def startsummary(self):
self.tw.sep("_", "summary")
def info(self, msg):
- if self.session.config.option.verbosity >= 2:
+ if self._get_verbosity() >= 2:
self.logline(msg)
def using(self, msg):
- if self.session.config.option.verbosity >= 1:
+ if self._get_verbosity() >= 1:
self.logline("using %s" % (msg,), bold=True)
def keyboard_interrupt(self):
@@ -298,15 +308,15 @@ class Reporter(object):
self.tw.line("%s" % msg, **opts)
def verbosity0(self, msg, **opts):
- if self.session.config.option.verbosity >= 0:
+ if self._get_verbosity() >= 0:
self.logline("%s" % msg, **opts)
def verbosity1(self, msg, **opts):
- if self.session.config.option.verbosity >= 1:
+ if self._get_verbosity() >= 1:
self.logline("%s" % msg, **opts)
def verbosity2(self, msg, **opts):
- if self.session.config.option.verbosity >= 2:
+ if self._get_verbosity() >= 2:
self.logline("%s" % msg, **opts)
# def log(self, msg):
@@ -364,14 +374,6 @@ class Session:
def runcommand(self):
self.report.using("tox-%s from %s" % (tox.__version__, tox.__file__))
- if self.config.minversion:
- minversion = NormalizedVersion(self.config.minversion)
- toxversion = NormalizedVersion(tox.__version__)
- if toxversion < minversion:
- self.report.error(
- "tox version is %s, required is at least %s" % (
- toxversion, minversion))
- raise SystemExit(1)
if self.config.option.showconfig:
self.showconfig()
elif self.config.option.listenvs:
@@ -539,8 +541,8 @@ class Session:
# write out version dependency information
action = self.newaction(venv, "envreport")
with action:
- pip = venv.getcommandpath("pip")
- output = venv._pcall([str(pip), "freeze"],
+ args = venv.envconfig.list_dependencies_command
+ output = venv._pcall(args,
cwd=self.config.toxinidir,
action=action)
# the output contains a mime-header, skip it