Commits

Ronny Pfannschmidt  committed 99e0c5d

$ sign and recursion for referential substitution

  • Participants
  • Parent commits a88207c

Comments (0)

Files changed (1)

File referential-substitution

 diff --git a/tests/test_config.py b/tests/test_config.py
 --- a/tests/test_config.py
 +++ b/tests/test_config.py
-@@ -480,6 +480,42 @@ class TestConfigTestEnv:
+@@ -480,6 +480,82 @@ class TestConfigTestEnv:
          argv = conf.commands
          assert argv[0] == ["cmd1", "hello"]
  
 +                pytest-cov
 +            [testenv:py24]
 +            deps=
-+                {testenv:deps}
++                {$testenv:deps}
 +                fun
 +        """
 +        conf = newconfig([], inisource).envconfigs['py24']
 +                mock
 +            [testenv]
 +            deps=
-+                {testing:pytest:deps}
-+                {testing:mock:deps}
++                {$testing:pytest:deps}
++                {$testing:mock:deps}
 +                fun
 +        """
 +        conf = newconfig([], inisource)
 +        packages = [dep.name for dep in env.deps]
 +        assert packages == ['pytest', 'pytest-cov', 'mock', 'fun']
 +
++    def test_multilevel_substitution(self, newconfig):
++        inisource="""
++            [testing:pytest]
++            deps=
++                pytest
++                pytest-cov
++            [testing:mock]
++            deps=
++                mock
++
++            [testing]
++            deps=
++                {$testing:pytest:deps}
++                {$testing:mock:deps}
++
++            [testenv]
++            deps=
++                {$testing:deps}
++                fun
++        """
++        conf = newconfig([], inisource)
++        env = conf.envconfigs['python']
++        packages = [dep.name for dep in env.deps]
++        assert packages == ['pytest', 'pytest-cov', 'mock', 'fun']
++
++    def test_recursive_substitution_cycle_fails(self, newconfig):
++        inisource="""
++            [testing:pytest]
++            deps=
++                {$testing:mock:deps}
++            [testing:mock]
++            deps=
++                {$testing:pytest:deps}
++
++            [testenv]
++            deps=
++                {$testing:pytest:deps}
++        """
++        py.test.raises(ValueError, newconfig, [], inisource)
++
 +
  class TestGlobalOptions:
      def test_notest(self, newconfig):
 diff --git a/tox/_config.py b/tox/_config.py
 --- a/tox/_config.py
 +++ b/tox/_config.py
-@@ -400,6 +400,11 @@ class IniReader:
+@@ -277,6 +277,7 @@ class IniReader:
+         self._cfg = cfgparser
+         self.fallbacksections = fallbacksections or []
+         self._subs = {}
++        self._subststack = []
+ 
+     def addsubstitions(self, _posargs=None, **kw):
+         self._subs.update(kw)
+@@ -386,7 +387,11 @@ class IniReader:
+             else:
+                 x = default
+         if replace and x and hasattr(x, 'replace'):
+-            x = self._replace(x)
++            self._subststack.append((section, name))
++            try:
++                x = self._replace(x)
++            finally:
++                assert self._subststack.pop() == (section, name)
+         #print "getdefault", section, name, "returned", repr(x)
+         return x
+ 
+@@ -400,6 +405,20 @@ class IniReader:
                      (key, envkey))
              return os.environ[envkey]
          if key not in self._subs:
-+            if ':' in key:
++            if key[0] == '$' and ':' in key:
 +                section, item = key.rsplit(':', 1)
++                section = section[1:]
++
 +                if section in self._cfg and item in self._cfg[section]:
-+                    return str(self._cfg[section][item])
++                    if (section, item) in self._subststack:
++                        raise ValueError('%s already in %s' %((section, item), self._subststack))
++                    x = str(self._cfg[section][item])
++                    self._subststack.append((section, item))
++                    try:
++                        return self._replace(x)
++                    finally:
++                        self._subststack.pop()
 +
              raise tox.exception.ConfigError(
                  "substitution key %r not found" % key)