Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move toxenv to tox_configure hook #78

Merged
merged 3 commits into from
Oct 28, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions docs/after.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,9 @@ that you are shipping a working release.
The accepted configuration keys
in the ``[travis:after]`` section are:

* ``toxenv``. Match with the running toxenvs,
based on the ``TOXENV`` environment variable,
which is set automatically by Tox-Travis.
* ``toxenv``. Match with the running toxenvs.
Expansion is allowed, and if set *all* environments listed
must be present in the ``TOXENV`` environment variable.
must be run in the current Tox run.
* ``travis``. Match with known Travis factors,
as is done in the ``[travis]`` section.
For instance, specifying that we should wait
Expand Down
12 changes: 6 additions & 6 deletions src/tox_travis/after.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,19 @@ def travis_after_monkeypatch():
def subcommand_test(self):
retcode = real_subcommand_test(self)
if retcode == 0 and self.config.option.travis_after:
travis_after() # No need to run if the tests failed anyway
# No need to run if the tests failed anyway
travis_after(self.config.envlist)
return retcode
tox.session.Session.subcommand_test = subcommand_test


def travis_after():
def travis_after(envlist):
"""Wait for all jobs to finish, then exit successfully."""
# after-all disabled for pull requests
if os.environ.get('TRAVIS_PULL_REQUEST', 'false') != 'false':
return

if not after_config_matches():
if not after_config_matches(envlist):
return # This is not the one that needs to wait

github_token = os.environ.get('GITHUB_TOKEN')
Expand Down Expand Up @@ -76,7 +77,7 @@ def travis_after():
print('All required jobs were successful.')


def after_config_matches():
def after_config_matches(envlist):
"""Determine if this job should wait for the others."""
config = py.iniconfig.IniConfig('tox.ini')
section = config.sections.get('travis:after', {})
Expand All @@ -86,8 +87,7 @@ def after_config_matches():

if 'toxenv' in section:
required = set(split_env(section['toxenv']))
# TOXENV should always be set if we've gotten this far
actual = set(split_env(os.environ['TOXENV']))
actual = set(envlist)
if required - actual:
return False

Expand Down
2 changes: 1 addition & 1 deletion src/tox_travis/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ def tox_addoption(parser):
help='Exit successfully after all Travis jobs complete successfully.')

if 'TRAVIS' in os.environ:
default_toxenv()
pypy_version_monkeypatch()


Expand All @@ -27,4 +26,5 @@ def tox_configure(config):
if 'TRAVIS' in os.environ:
if config.option.travis_after:
travis_after_monkeypatch()
default_toxenv(config)
override_ignore_outcome(config)
71 changes: 52 additions & 19 deletions src/tox_travis/toxenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,76 @@
from .utils import TRAVIS_FACTORS, parse_dict


def default_toxenv():
"""Default TOXENV automatically based on the Travis environment."""
if 'TOXENV' in os.environ:
def default_toxenv(config):
"""Default envlist automatically based on the Travis environment."""
if 'TOXENV' in os.environ or config.option.env:
return # Skip any processing if already set

config = py.iniconfig.IniConfig('tox.ini')
ini = py.iniconfig.IniConfig('tox.ini')

# Find the envs that tox knows about
declared_envs = get_declared_envs(config)
declared_envs = get_declared_envs(ini)

# Find all the envs for all the desired factors given
desired_factors = get_desired_factors(config)
desired_factors = get_desired_factors(ini)

# Reduce desired factors
desired_envs = ['-'.join(env) for env in product(*desired_factors)]

# Find matching envs
matched = match_envs(declared_envs, desired_envs,
passthru=len(desired_factors) == 1)
os.environ.setdefault('TOXENV', ','.join(matched))

# Make the envconfig for undeclared matched envs
autogen_envconfigs(config, set(matched) - set(config.envconfigs))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part puzzles me, comparing this to #61, I don't understand what exactly autgen_envconfigs does and why it's necessary. It's not clear to me what's depending on the generated envconfigs, and why this wasn't necessary in #61.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a case that #61 didn't cover, and exists for backward compatibility with the first release of tox-travis. With that first release, you would declare what environments got run for each version of Python. It didn't look at the environments that were listed at all, it just sent the string in the configuration verbatim, through the TOXENV environment variable. If the specified environment is not declared in the tox config, Tox will still happily run that environment, doing the best it can based on the settings it has.

When we switched to matching based on factors, so that by default on Python 3.4 all envs with py34 in the name would run, that obviously was inherently in conflict with the original mechanism. However, I was able to maintain partial backward compatibility by saying that if there was only one "factor" being matched (python, which is automatic), and no environments matched it, then whatever the setting is will be used verbatim, in the same way as was done in the original version.

This is a wart, but fixing it means a breaking change. This workaround allows me to get this change in without having to worry about a breaking change at this time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and as far as what it actually does. Before the tox_configure hook, Tox looks at all the environments, and creates a envconfig for each one. The envconfig is the configuration of how to run the environment, with default settings resolved. What this does is notice when we've ended up with an envconfig that Tox didn't know about to begin with, and make an envconfig for it the same way Tox would have if it had been specified in TOXENV.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a wart, but fixing it means a breaking change. This workaround allows me to get this change in without having to worry about a breaking change at this time.

Gotcha, this is compatibility code, and would presumably not be present in the next major release.


def get_declared_envs(config):
"""Get the full list of envs from the tox config.
config.envlist = matched


def autogen_envconfigs(config, envs):
"""Make the envconfigs for undeclared envs.

This is a stripped-down version of parseini.__init__ made for making
an envconfig.
"""
prefix = 'tox' if config.toxinipath.basename == 'setup.cfg' else None
reader = tox.config.SectionReader("tox", config._cfg, prefix=prefix)
distshare_default = "{homedir}/.tox/distshare"
reader.addsubstitutions(toxinidir=config.toxinidir,
homedir=config.homedir)

reader.addsubstitutions(toxworkdir=config.toxworkdir)
config.distdir = reader.getpath("distdir", "{toxworkdir}/dist")
reader.addsubstitutions(distdir=config.distdir)
config.distshare = reader.getpath("distshare", distshare_default)
reader.addsubstitutions(distshare=config.distshare)

# Create the undeclared envs
for env in envs:
make_envconfig = tox.config.parseini.make_envconfig
# Dig past the unbound method in Python 2
make_envconfig = getattr(make_envconfig, '__func__', make_envconfig)

section = tox.config.testenvprefix + env
config.envconfigs[env] = make_envconfig(
config, env, section, reader._subs, config)


def get_declared_envs(ini):
"""Get the full list of envs from the tox ini.

This notably also includes envs that aren't in the envlist,
but are declared by having their own testenv:envname section.

The envs are expected in a particular order. First the ones
declared in the envlist, then the other testenvs in order.
"""
tox_section = config.sections.get('tox', {})
tox_section = ini.sections.get('tox', {})
envlist = split_env(tox_section.get('envlist', []))

# Add additional envs that are declared as sections in the config
# Add additional envs that are declared as sections in the ini
section_envs = [
section[8:] for section in sorted(config.sections, key=config.lineof)
section[8:] for section in sorted(ini.sections, key=ini.lineof)
if section.startswith('testenv:')
]

Expand Down Expand Up @@ -94,7 +127,7 @@ def get_default_envlist(version):
return guess_python_env()


def get_desired_factors(config):
def get_desired_factors(ini):
"""Get the list of desired envs per declared factor.

Look at all the accepted configuration locations, and give a list
Expand Down Expand Up @@ -126,18 +159,18 @@ def get_desired_factors(config):
to apply to this environment.
"""
# Find configuration based on known travis factors
travis_section = config.sections.get('travis', {})
travis_section = ini.sections.get('travis', {})
found_factors = [
(factor, parse_dict(travis_section[factor]))
for factor in TRAVIS_FACTORS
if factor in travis_section
]

# Backward compatibility with the old tox:travis section
if 'tox:travis' in config.sections:
if 'tox:travis' in ini.sections:
print('The [tox:travis] section is deprecated in favor of'
' the "python" key of the [travis] section.', file=sys.stderr)
found_factors.append(('python', config.sections['tox:travis']))
found_factors.append(('python', ini.sections['tox:travis']))

# Inject any needed autoenv
version = os.environ.get('TRAVIS_PYTHON_VERSION')
Expand All @@ -158,7 +191,7 @@ def get_desired_factors(config):
for factor, mapping in found_factors
] + [
(name, parse_dict(value))
for name, value in config.sections.get('travis:env', {}).items()
for name, value in ini.sections.get('travis:env', {}).items()
]

# Choose the correct envlists based on the factor values
Expand Down Expand Up @@ -205,5 +238,5 @@ def override_ignore_outcome(config):
tox_config = py.iniconfig.IniConfig('tox.ini')
travis_reader = tox.config.SectionReader("travis", tox_config)
if travis_reader.getbool('unignore_outcomes', False):
for env in config.envlist:
config.envconfigs[env].ignore_outcome = False
for envconfig in config.envconfigs.values():
envconfig.ignore_outcome = False
16 changes: 8 additions & 8 deletions tests/test_after.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_pull_request(self, mocker, monkeypatch, capsys):
monkeypatch.setenv('TRAVIS', 'true')
monkeypatch.setenv('TRAVIS_PULL_REQUEST', '1')

travis_after()
travis_after(mocker.Mock())

out, err = capsys.readouterr()
assert out == ''
Expand All @@ -25,7 +25,7 @@ def test_no_github_token(self, mocker, monkeypatch, capsys):
return_value=True)
monkeypatch.setenv('TRAVIS', 'true')
with pytest.raises(SystemExit) as excinfo:
travis_after()
travis_after(mocker.Mock())

assert excinfo.value.code == 32
out, err = capsys.readouterr()
Expand All @@ -38,7 +38,7 @@ def test_travis_environment_build_id(self, mocker, monkeypatch, capsys):
monkeypatch.setenv('TRAVIS', 'true')
monkeypatch.setenv('GITHUB_TOKEN', 'spamandeggs')
with pytest.raises(SystemExit) as excinfo:
travis_after()
travis_after(mocker.Mock())

assert excinfo.value.code == 34
out, err = capsys.readouterr()
Expand All @@ -52,7 +52,7 @@ def test_travis_environment_job_number(self, mocker, monkeypatch, capsys):
monkeypatch.setenv('GITHUB_TOKEN', 'spamandeggs')
monkeypatch.setenv('TRAVIS_BUILD_ID', '1234')
with pytest.raises(SystemExit) as excinfo:
travis_after()
travis_after(mocker.Mock())

assert excinfo.value.code == 34
out, err = capsys.readouterr()
Expand All @@ -69,7 +69,7 @@ def test_travis_environment_api_url(self, mocker, monkeypatch, capsys):
# TRAVIS_API_URL is set to a reasonable default
monkeypatch.setenv('TRAVIS_API_URL', '')
with pytest.raises(SystemExit) as excinfo:
travis_after()
travis_after(mocker.Mock())

assert excinfo.value.code == 34
out, err = capsys.readouterr()
Expand All @@ -86,7 +86,7 @@ def test_travis_env_polling_interval(self, mocker, monkeypatch, capsys):
# TRAVIS_POLLING_INTERVAL is set to a reasonable default
monkeypatch.setenv('TRAVIS_POLLING_INTERVAL', 'xbe')
with pytest.raises(SystemExit) as excinfo:
travis_after()
travis_after(mocker.Mock())

assert excinfo.value.code == 33
out, err = capsys.readouterr()
Expand Down Expand Up @@ -186,7 +186,7 @@ def get_json(url, auth=None, data=None):
get_json.responses = iter(responses)

mocker.patch('tox_travis.after.get_json', side_effect=get_json)
travis_after()
travis_after(mocker.Mock())
out, err = capsys.readouterr()
assert 'All required jobs were successful.' in out

Expand Down Expand Up @@ -289,7 +289,7 @@ def get_json(url, auth=None, data=None):
mocker.patch('tox_travis.after.get_json', side_effect=get_json)

with pytest.raises(SystemExit) as excinfo:
travis_after()
travis_after(mocker.Mock())

assert excinfo.value.code == 35
out, err = capsys.readouterr()
Expand Down
6 changes: 3 additions & 3 deletions tests/test_toxenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ def test_travis_ignore_outcome(self, tmpdir, monkeypatch):
"""Test ignore_outcome without setting obey_outcomes."""
tox_ini = tox_ini_ignore_outcome
with self.configure(
tmpdir, monkeypatch, tox_ini, travis_language='generic'
tmpdir, monkeypatch, tox_ini, travis_version='3.4'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, what was the reason behind this change?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the generic language wasn't even being used in the tox config, so it turns out that it was relying on the broken behavior from #75. And that broken behavior was fixed as a side-effect of this Pull Request, so this needed to be changed to match.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that makes sense.

):
config = self.tox_config()
assert config["testenv:py34"]["ignore_outcome"] == "True"
Expand All @@ -426,7 +426,7 @@ def test_travis_ignore_outcome_unignore_outcomes(self, tmpdir,
"""Test ignore_outcome setting unignore_outcomes = False."""
tox_ini = tox_ini_ignore_outcome_unignore_outcomes
with self.configure(
tmpdir, monkeypatch, tox_ini, travis_language='generic'
tmpdir, monkeypatch, tox_ini, travis_version='3.4'
):
config = self.tox_config()
assert config["testenv:py34"]["ignore_outcome"] == "False"
Expand All @@ -436,7 +436,7 @@ def test_travis_ignore_outcome_not_unignore_outcomes(self, tmpdir,
"""Test ignore_outcome setting unignore_outcomes = False (default)."""
tox_ini = tox_ini_ignore_outcome_not_unignore_outcomes
with self.configure(
tmpdir, monkeypatch, tox_ini, travis_language='generic'
tmpdir, monkeypatch, tox_ini, travis_version='3.4'
):
config = self.tox_config()
assert config["testenv:py34"]["ignore_outcome"] == "True"
Expand Down