1 new commit in tox:
https://bitbucket.org/hpk42/tox/commits/5630201c3d53/
Changeset: 5630201c3d53
User: cjerdonek
Date: 2013-11-14 10:51:26
Summary: Address issue #125 by adding a --hashseed command-line option.
This commit also causes Tox to set PYTHONHASHSEED for test commands to a
random integer generated when tox is invoked. See the issue here:
https://bitbucket.org/hpk42/tox/issue/125
Affected #: 5 files
diff -r 72ba41dbebee260b19f8ef1a337c83d45ab8fcf3 -r
5630201c3d53266e9a8fb0b9b37310256ddb213e doc/example/basic.txt
--- a/doc/example/basic.txt
+++ b/doc/example/basic.txt
@@ -175,6 +175,27 @@
from the ``subdir`` below the directory where your ``tox.ini``
file resides.
+special handling of PYTHONHASHSEED
+-------------------------------------------
+
+.. versionadded:: 1.6.2
+
+By default, Tox sets PYTHONHASHSEED_ for test commands to a random integer
+generated when ``tox`` is invoked. This mimics Python's hash randomization
+enabled by default starting `in Python 3.3`_. To aid in reproducing test
+failures, Tox displays the value of ``PYTHONHASHSEED`` in the test output.
+
+You can tell Tox to use an explicit hash seed value via the ``--hashseed``
+command-line option to ``tox``. You can also override the hash seed value
+per test environment in ``tox.ini`` as follows::
+
+ [testenv:hash]
+ setenv =
+ PYTHONHASHSEED = 100
+
+.. _`in Python 3.3`:
http://docs.python.org/3/whatsnew/3.3.html#builtin-functions-and-types
+.. _PYTHONHASHSEED:
http://docs.python.org/using/cmdline.html#envvar-PYTHONHASHSEED
+
Integration with setuptools/distribute test commands
----------------------------------------------------
diff -r 72ba41dbebee260b19f8ef1a337c83d45ab8fcf3 -r
5630201c3d53266e9a8fb0b9b37310256ddb213e tests/test_config.py
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -5,6 +5,7 @@
from textwrap import dedent
import py
+import tox._config
from tox._config import *
from tox._config import _split_env
@@ -405,7 +406,13 @@
assert envconfig.sitepackages == False
assert envconfig.develop == False
assert envconfig.envlogdir == envconfig.envdir.join("log")
- assert envconfig.setenv is None
+ assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED']
+ hashseed = envconfig.setenv['PYTHONHASHSEED']
+ assert isinstance(hashseed, str)
+ # The following line checks that hashseed parses to an integer.
+ int_hashseed = int(hashseed)
+ # hashseed is random by default, so we can't assert a specific value.
+ assert int_hashseed > 0
def test_installpkg_tops_develop(self, newconfig):
config = newconfig(["--installpkg=abc"], """
@@ -899,6 +906,120 @@
assert env.basepython == "python2.4"
assert env.commands == [['xyz']]
+class TestHashseedOption:
+
+ def _get_envconfigs(self, newconfig, args=None, tox_ini=None,
+ make_hashseed=None):
+ if args is None:
+ args = []
+ if tox_ini is None:
+ tox_ini = """
+ [testenv]
+ """
+ if make_hashseed is None:
+ make_hashseed = lambda: '123456789'
+ original_make_hashseed = tox._config.make_hashseed
+ tox._config.make_hashseed = make_hashseed
+ try:
+ config = newconfig(args, tox_ini)
+ finally:
+ tox._config.make_hashseed = original_make_hashseed
+ return config.envconfigs
+
+ def _get_envconfig(self, newconfig, args=None, tox_ini=None):
+ envconfigs = self._get_envconfigs(newconfig, args=args,
+ tox_ini=tox_ini)
+ return envconfigs["python"]
+
+ def _check_hashseed(self, envconfig, expected):
+ assert envconfig.setenv == {'PYTHONHASHSEED': expected}
+
+ def _check_testenv(self, newconfig, expected, args=None, tox_ini=None):
+ envconfig = self._get_envconfig(newconfig, args=args, tox_ini=tox_ini)
+ self._check_hashseed(envconfig, expected)
+
+ def test_default(self, tmpdir, newconfig):
+ self._check_testenv(newconfig, '123456789')
+
+ def test_passing_integer(self, tmpdir, newconfig):
+ args = ['--hashseed', '1']
+ self._check_testenv(newconfig, '1', args=args)
+
+ def test_passing_string(self, tmpdir, newconfig):
+ args = ['--hashseed', 'random']
+ self._check_testenv(newconfig, 'random', args=args)
+
+ def test_passing_empty_string(self, tmpdir, newconfig):
+ args = ['--hashseed', '']
+ self._check_testenv(newconfig, '', args=args)
+
+ def test_passing_no_argument(self, tmpdir, newconfig):
+ """Test that passing no arguments to --hashseed is not allowed."""
+ args = ['--hashseed']
+ try:
+ self._check_testenv(newconfig, '', args=args)
+ except SystemExit:
+ e = sys.exc_info()[1]
+ assert e.code == 2
+ return
+ assert False # getting here means we failed the test.
+
+ def test_setenv(self, tmpdir, newconfig):
+ """Check that setenv takes precedence."""
+ tox_ini = """
+ [testenv]
+ setenv =
+ PYTHONHASHSEED = 2
+ """
+ self._check_testenv(newconfig, '2', tox_ini=tox_ini)
+ args = ['--hashseed', '1']
+ self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini)
+
+ def test_noset(self, tmpdir, newconfig):
+ args = ['--hashseed', 'noset']
+ envconfig = self._get_envconfig(newconfig, args=args)
+ assert envconfig.setenv is None
+
+ def test_noset_with_setenv(self, tmpdir, newconfig):
+ tox_ini = """
+ [testenv]
+ setenv =
+ PYTHONHASHSEED = 2
+ """
+ args = ['--hashseed', 'noset']
+ self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini)
+
+ def test_one_random_hashseed(self, tmpdir, newconfig):
+ """Check that different testenvs use the same random seed."""
+ tox_ini = """
+ [testenv:hash1]
+ [testenv:hash2]
+ """
+ next_seed = [1000]
+ # This function is guaranteed to generate a different value each time.
+ def make_hashseed():
+ next_seed[0] += 1
+ return str(next_seed[0])
+ # Check that make_hashseed() works.
+ assert make_hashseed() == '1001'
+ envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini,
+ make_hashseed=make_hashseed)
+ self._check_hashseed(envconfigs["hash1"], '1002')
+ # Check that hash2's value is not '1003', for example.
+ self._check_hashseed(envconfigs["hash2"], '1002')
+
+ def test_setenv_in_one_testenv(self, tmpdir, newconfig):
+ """Check using setenv in one of multiple testenvs."""
+ tox_ini = """
+ [testenv:hash1]
+ setenv =
+ PYTHONHASHSEED = 2
+ [testenv:hash2]
+ """
+ envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini)
+ self._check_hashseed(envconfigs["hash1"], '2')
+ self._check_hashseed(envconfigs["hash2"], '123456789')
+
class TestIndexServer:
def test_indexserver(self, tmpdir, newconfig):
config = newconfig("""
diff -r 72ba41dbebee260b19f8ef1a337c83d45ab8fcf3 -r
5630201c3d53266e9a8fb0b9b37310256ddb213e tests/test_venv.py
--- a/tests/test_venv.py
+++ b/tests/test_venv.py
@@ -2,6 +2,7 @@
import tox
import pytest
import os, sys
+import tox._config
from tox._venv import *
py25calls = int(sys.version_info[:2] == (2,5))
@@ -231,6 +232,20 @@
venv.update()
mocksession.report.expect("verbosity0", "*recreate*")
+def test_test_hashseed_is_in_output(newmocksession):
+ original_make_hashseed = tox._config.make_hashseed
+ tox._config.make_hashseed = lambda: '123456789'
+ try:
+ mocksession = newmocksession([], '''
+ [testenv]
+ ''')
+ finally:
+ tox._config.make_hashseed = original_make_hashseed
+ venv = mocksession.getenv('python')
+ venv.update()
+ venv.test()
+ mocksession.report.expect("verbosity0", "python runtests:
PYTHONHASHSEED='123456789'")
+
def test_test_runtests_action_command_is_in_output(newmocksession):
mocksession = newmocksession([], '''
[testenv]
diff -r 72ba41dbebee260b19f8ef1a337c83d45ab8fcf3 -r
5630201c3d53266e9a8fb0b9b37310256ddb213e tox/_config.py
--- a/tox/_config.py
+++ b/tox/_config.py
@@ -1,6 +1,7 @@
import argparse
import distutils.sysconfig
import os
+import random
import sys
import re
import shlex
@@ -117,6 +118,12 @@
"all commands and results involved. This will turn off "
"pass-through output from running test commands which is "
"instead captured into the json result file.")
+ # We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED.
+ parser.add_argument("--hashseed", action="store",
+ metavar="SEED", default=None,
+ help="set PYTHONHASHSEED to SEED before running commands. "
+ "Defaults to a random integer in the range 1 to 4294967295. "
+ "Passing 'noset' suppresses this behavior.")
parser.add_argument("args", nargs="*",
help="additional arguments available to command positional
substitution")
return parser
@@ -180,6 +187,9 @@
except Exception:
return None
+def make_hashseed():
+ return str(random.randint(1, 4294967295))
+
class parseini:
def __init__(self, config, inipath):
config.toxinipath = inipath
@@ -200,6 +210,13 @@
else:
raise ValueError("invalid context")
+ if config.option.hashseed is None:
+ hashseed = make_hashseed()
+ elif config.option.hashseed == 'noset':
+ hashseed = None
+ else:
+ hashseed = config.option.hashseed
+ config.hashseed = hashseed
reader.addsubstitutions(toxinidir=config.toxinidir,
homedir=config.homedir)
@@ -306,7 +323,11 @@
arg = vc.changedir.bestrelpath(origpath)
args.append(arg)
reader.addsubstitutions(args)
- vc.setenv = reader.getdict(section, 'setenv')
+ setenv = {}
+ if config.hashseed is not None:
+ setenv['PYTHONHASHSEED'] = config.hashseed
+ setenv.update(reader.getdict(section, 'setenv'))
+ vc.setenv = setenv
if not vc.setenv:
vc.setenv = None
diff -r 72ba41dbebee260b19f8ef1a337c83d45ab8fcf3 -r
5630201c3d53266e9a8fb0b9b37310256ddb213e tox/_venv.py
--- a/tox/_venv.py
+++ b/tox/_venv.py
@@ -336,14 +336,13 @@
self.run_install_command(packages=packages, options=options,
action=action, extraenv=extraenv)
- def _getenv(self):
- env = self.envconfig.setenv
- if env:
- env_arg = os.environ.copy()
- env_arg.update(env)
- else:
- env_arg = None
- return env_arg
+ def _getenv(self, extraenv={}):
+ env = os.environ.copy()
+ setenv = self.envconfig.setenv
+ if setenv:
+ env.update(setenv)
+ env.update(extraenv)
+ return env
def test(self, redirect=False):
action = self.session.newaction(self, "runtests")
@@ -351,6 +350,9 @@
self.status = 0
self.session.make_emptydir(self.envconfig.envtmpdir)
cwd = self.envconfig.changedir
+ env = self._getenv()
+ # Display PYTHONHASHSEED to assist with reproducibility.
+ action.setactivity("runtests", "PYTHONHASHSEED=%r" %
env.get('PYTHONHASHSEED'))
for i, argv in enumerate(self.envconfig.commands):
# have to make strings as _pcall changes argv[0] to a local()
# happens if the same environment is invoked twice
@@ -380,8 +382,7 @@
old = self.patchPATH()
try:
args[0] = self.getcommandpath(args[0], venv, cwd)
- env = self._getenv() or os.environ.copy()
- env.update(extraenv)
+ env = self._getenv(extraenv)
return action.popen(args, cwd=cwd, env=env, redirect=redirect)
finally:
os.environ['PATH'] = old
Repository URL: https://bitbucket.org/hpk42/tox/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
_______________________________________________
pytest-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pytest-commit