summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorholger krekel <holger@merlinux.eu>2015-11-13 10:49:05 +0100
committerholger krekel <holger@merlinux.eu>2015-11-13 10:49:05 +0100
commit483d1a4b61b7088b7ba90894683ace252c4f97b7 (patch)
treee81152d0da1b391e064c45b15329116ed042b0e6
parent78efe5c87d1ad709d309f998a8c9c252e4fc44a4 (diff)
downloadtox-483d1a4b61b7088b7ba90894683ace252c4f97b7.tar.gz
address issue285 but not fully resolve it: setenv processing now works,
making all config tests pass except for one substitution issue which requires yet some more logic, probably.
-rw-r--r--tests/test_config.py32
-rw-r--r--tox/config.py109
2 files changed, 83 insertions, 58 deletions
diff --git a/tests/test_config.py b/tests/test_config.py
index 44c6e7d..9ba2baa 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -283,18 +283,9 @@ class TestIniParserAgainstCommandsKey:
commands =
ls {env:TEST}
""")
- reader = SectionReader("testenv:py27", config._cfg)
- x = reader.getargvlist("commands")
- assert x == [
- "ls testvalue".split()
- ]
- assert x != [
- "ls {env:TEST}".split()
- ]
- y = reader.getargvlist("setenv")
- assert y == [
- "TEST=testvalue".split()
- ]
+ envconfig = config.envconfigs["py27"]
+ assert envconfig.commands == [["ls", "testvalue"]]
+ assert envconfig.setenv["TEST"] == "testvalue"
class TestIniParser:
@@ -1599,7 +1590,19 @@ class TestHashseedOption:
class TestSetenv:
- @pytest.mark.xfail(reason="fix pending")
+ def test_getdict_lazy(self, tmpdir, newconfig):
+ config = newconfig("""
+ [testenv:X]
+ key0 =
+ key1 = {env:X}
+ key2 = {env:X:1}
+ """)
+ envconfig = config.envconfigs["X"]
+ val = envconfig._reader.getdict_lazy("key0")
+ assert val == {"key1": "{env:X}",
+ "key2": "{env:X:1}"}
+
+
def test_setenv_uses_os_environ(self, tmpdir, newconfig, monkeypatch):
monkeypatch.setenv("X", "1")
config = newconfig("""
@@ -1609,7 +1612,6 @@ class TestSetenv:
""")
assert config.envconfigs["env1"].setenv["X"] == "1"
- @pytest.mark.xfail(reason="fix pending")
def test_setenv_default_os_environ(self, tmpdir, newconfig, monkeypatch):
monkeypatch.delenv("X", raising=False)
config = newconfig("""
@@ -1619,7 +1621,6 @@ class TestSetenv:
""")
assert config.envconfigs["env1"].setenv["X"] == "2"
- @pytest.mark.xfail(reason="fix pending")
def test_setenv_uses_other_setenv(self, tmpdir, newconfig):
config = newconfig("""
[testenv:env1]
@@ -1629,7 +1630,6 @@ class TestSetenv:
""")
assert config.envconfigs["env1"].setenv["X"] == "5"
- @pytest.mark.xfail(reason="fix pending")
def test_setenv_recursive_direct(self, tmpdir, newconfig):
config = newconfig("""
[testenv:env1]
diff --git a/tox/config.py b/tox/config.py
index 6db1e3a..7a500f0 100644
--- a/tox/config.py
+++ b/tox/config.py
@@ -323,11 +323,39 @@ def tox_addoption(parser):
parser.add_argument("args", nargs="*",
help="additional arguments available to command positional substitution")
- # add various core venv interpreter attributes
parser.add_testenv_attribute(
name="envdir", type="path", default="{toxworkdir}/{envname}",
help="venv directory")
+ # add various core venv interpreter attributes
+ def setenv(testenv_config, value):
+ setenv = value
+ reader = testenv_config._reader
+
+ # we need to resolve environment variable substitution
+
+ replacing = [] # for detecting direct recursion
+ def setenv_reader(name):
+ if name in setenv and name not in replacing:
+ return setenv[name]
+ return os.environ.get(name)
+ reader.set_envreader(setenv_reader)
+
+ for name, value in setenv.items():
+ replacing.append(name)
+ setenv[name] = reader._replace(value)
+ replacing.pop()
+
+ config = testenv_config.config
+ if "PYTHONHASHSEED" not in setenv and config.hashseed is not None:
+ setenv['PYTHONHASHSEED'] = config.hashseed
+ return setenv
+
+ parser.add_testenv_attribute(
+ name="setenv", type="dict_lazy", postprocess=setenv,
+ help="list of X=Y lines with environment variable settings")
+
+
def basepython_default(testenv_config, value):
if value is None:
for f in testenv_config.factors:
@@ -385,17 +413,6 @@ def tox_addoption(parser):
name="recreate", type="bool", default=False, postprocess=recreate,
help="always recreate this test environment.")
- def setenv(testenv_config, value):
- setenv = value
- config = testenv_config.config
- if "PYTHONHASHSEED" not in setenv and config.hashseed is not None:
- setenv['PYTHONHASHSEED'] = config.hashseed
- return setenv
-
- parser.add_testenv_attribute(
- name="setenv", type="dict", postprocess=setenv,
- help="list of X=Y lines with environment variable settings")
-
def passenv(testenv_config, value):
# Flatten the list to deal with space-separated values.
value = list(
@@ -518,21 +535,15 @@ class TestenvConfig:
@property
def envbindir(self):
""" path to directory where scripts/binaries reside. """
- if (sys.platform == "win32"
- and "jython" not in self.basepython
- and "pypy" not in self.basepython):
+ if sys.platform == "win32":
return self.envdir.join("Scripts")
else:
return self.envdir.join("bin")
@property
def envpython(self):
- """ path to python/jython executable. """
- if "jython" in str(self.basepython):
- name = "jython"
- else:
- name = "python"
- return self.envbindir.join(name)
+ """ path to python executable. """
+ return self.envbindir.join(self.basepython)
# no @property to avoid early calling (see callable(subst[key]) checks)
def envsitepackagesdir(self):
@@ -699,7 +710,7 @@ class parseini:
for env_attr in config._testenv_attr:
atype = env_attr.type
- if atype in ("bool", "path", "string", "dict", "argv", "argvlist"):
+ if atype in ("bool", "path", "string", "dict", "dict_lazy", "argv", "argvlist"):
meth = getattr(reader, "get" + atype)
res = meth(env_attr.name, env_attr.default)
elif atype == "space-separated-list":
@@ -807,6 +818,13 @@ class SectionReader:
self.factors = factors
self._subs = {}
self._subststack = []
+ self._envreader = os.environ.get
+
+ def set_envreader(self, envreader):
+ self._envreader = envreader
+
+ def get_environ_value(self, name):
+ return self._envreader(name)
def addsubstitutions(self, _posargs=None, **kw):
self._subs.update(kw)
@@ -826,17 +844,24 @@ class SectionReader:
return [x.strip() for x in s.split(sep) if x.strip()]
def getdict(self, name, default=None, sep="\n"):
- s = self.getstring(name, None)
- if s is None:
+ value = self.getstring(name, None)
+ return self._getdict(value, default=default, sep=sep)
+
+ def getdict_lazy(self, name, default=None, sep="\n"):
+ value = self.getstring(name, None, replace="noenv")
+ return self._getdict(value, default=default, sep=sep)
+
+ def _getdict(self, value, default, sep):
+ if value is None:
return default or {}
- value = {}
- for line in s.split(sep):
+ d = {}
+ for line in value.split(sep):
if line.strip():
name, rest = line.split('=', 1)
- value[name.strip()] = rest.strip()
+ d[name.strip()] = rest.strip()
- return value
+ return d
def getbool(self, name, default=None):
s = self.getstring(name, default)
@@ -878,7 +903,7 @@ class SectionReader:
x = self._apply_factors(x)
if replace and x and hasattr(x, 'replace'):
- x = self._replace(x, name=name)
+ x = self._replace(x, name=name, opt_replace_env=(replace!="noenv"))
# print "getstring", self.section_name, name, "returned", repr(x)
return x
@@ -895,14 +920,14 @@ class SectionReader:
lines = s.strip().splitlines()
return '\n'.join(filter(None, map(factor_line, lines)))
- def _replace(self, value, name=None, section_name=None):
+ def _replace(self, value, name=None, section_name=None, opt_replace_env=True):
if '{' not in value:
return value
section_name = section_name if section_name else self.section_name
self._subststack.append((section_name, name))
try:
- return Replacer(self).do_replace(value)
+ return Replacer(self, opt_replace_env=opt_replace_env).do_replace(value)
finally:
assert self._subststack.pop() == (section_name, name)
@@ -918,7 +943,7 @@ class Replacer:
re.VERBOSE)
- def __init__(self, reader, opt_replace_env=True):
+ def __init__(self, reader, opt_replace_env):
self.reader = reader
self.opt_replace_env = opt_replace_env
@@ -942,14 +967,14 @@ class Replacer:
"Malformed substitution; no substitution type provided")
if sub_type == "env":
- assert self.opt_replace_env
- return self._replace_env(match)
+ if self.opt_replace_env:
+ return self._replace_env(match)
+ return "{env:%s}" %(g["substitution_value"])
if sub_type != None:
raise tox.exception.ConfigError("No support for the %s substitution type" % sub_type)
return self._replace_substitution(match)
def _replace_env(self, match):
- env_list = self.reader.getdict('setenv')
match_value = match.group('substitution_value')
if not match_value:
raise tox.exception.ConfigError(
@@ -963,15 +988,14 @@ class Replacer:
else:
envkey = match_value
- if envkey not in os.environ and default is None:
- if envkey not in env_list and default is None:
+ envvalue = self.reader.get_environ_value(envkey)
+ if envvalue is None:
+ if default is None:
raise tox.exception.ConfigError(
"substitution env:%r: unknown environment variable %r" %
(envkey, envkey))
- if envkey in os.environ:
- return os.environ.get(envkey, default)
- else:
- return env_list.get(envkey, default)
+ return default
+ return envvalue
def _substitute_from_other_section(self, key):
if key.startswith("[") and "]" in key:
@@ -983,7 +1007,8 @@ class Replacer:
raise ValueError('%s already in %s' % (
(section, item), self.reader._subststack))
x = str(cfg[section][item])
- return self.reader._replace(x, name=item, section_name=section)
+ return self.reader._replace(x, name=item, section_name=section,
+ opt_replace_env=self.opt_replace_env)
raise tox.exception.ConfigError(
"substitution key %r not found" % key)