diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41d395a29..4068a590b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,17 @@ repos: +- repo: https://github.com/ambv/black + rev: 18.4a4 + hooks: + - id: black + args: [--line-length=99, --safe] + python_version: python3.6 - repo: https://github.com/pre-commit/pre-commit-hooks - sha: v1.1.1 + rev: v1.2.3 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - id: check-yaml - id: debug-statements - id: flake8 - repo: https://github.com/asottile/pyupgrade - sha: v1.2.0 + rev: v1.2.0 hooks: - id: pyupgrade diff --git a/README.rst b/README.rst index c3878abfb..703978858 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,9 @@ .. image:: https://readthedocs.org/projects/tox/badge/?version=latest :target: http://tox.readthedocs.io/en/latest/?badge=latest :alt: Documentation status +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black + :alt: Code style: black tox automation project ====================== diff --git a/doc/conf.py b/doc/conf.py index 29cdef394..da291f631 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -5,57 +5,59 @@ from pkg_resources import get_distribution sys.path.insert(0, os.path.dirname(__file__)) -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.extlinks', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode'] +extensions = [ + "sphinx.ext.autodoc", "sphinx.ext.extlinks", "sphinx.ext.intersphinx", "sphinx.ext.viewcode" +] -project = u'tox' +project = u"tox" _full_version = get_distribution(project).version -release = _full_version.split('+', 1)[0] -version = '.'.join(release.split('.')[:2]) +release = _full_version.split("+", 1)[0] +version = ".".join(release.split(".")[:2]) -author = 'holger krekel and others' +author = "holger krekel and others" year = date.today().year -copyright = u'2010-{}, {}'.format(year, author) +copyright = u"2010-{}, {}".format(year, author) -master_doc = 'index' -source_suffix = '.rst' +master_doc = "index" +source_suffix = ".rst" -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] -templates_path = ['_templates'] -pygments_style = 'sphinx' -html_theme = 'alabaster' -html_logo = 'img/tox.png' -html_favicon = 'img/toxfavi.ico' -html_static_path = ['_static'] +templates_path = ["_templates"] +pygments_style = "sphinx" +html_theme = "alabaster" +html_logo = "img/tox.png" +html_favicon = "img/toxfavi.ico" +html_static_path = ["_static"] html_show_sourcelink = False -htmlhelp_basename = '{}doc'.format(project) -latex_documents = [('index', 'tox.tex', u'{} Documentation'.format(project), - author, 'manual')] -man_pages = [('index', project, u'{} Documentation'.format(project), - [author], 1)] +htmlhelp_basename = "{}doc".format(project) +latex_documents = [("index", "tox.tex", u"{} Documentation".format(project), author, "manual")] +man_pages = [("index", project, u"{} Documentation".format(project), [author], 1)] epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright -intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {"https://docs.python.org/": None} def setup(app): # from sphinx.ext.autodoc import cut_lines # app.connect('autodoc-process-docstring', cut_lines(4, what=['module'])) - app.add_description_unit('confval', 'confval', - objname='configuration value', - indextemplate='pair: %s; configuration value') + app.add_description_unit( + "confval", + "confval", + objname="configuration value", + indextemplate="pair: %s; configuration value", + ) -tls_cacerts = os.getenv('SSL_CERT_FILE') # we don't care here about the validity of certificates +tls_cacerts = os.getenv("SSL_CERT_FILE") # we don't care here about the validity of certificates linkcheck_timeout = 30 -linkcheck_ignore = [r'http://holgerkrekel.net'] +linkcheck_ignore = [r"http://holgerkrekel.net"] -extlinks = {'issue': ('https://github.com/tox-dev/tox/issues/%s', '#'), - 'pull': ('https://github.com/tox-dev/tox/pull/%s', 'p'), - 'user': ('https://github.com/%s', '@')} +extlinks = { + "issue": ("https://github.com/tox-dev/tox/issues/%s", "#"), + "pull": ("https://github.com/tox-dev/tox/pull/%s", "p"), + "user": ("https://github.com/%s", "@"), +} diff --git a/setup.py b/setup.py index 4ae2ad0c6..d2cb13225 100644 --- a/setup.py +++ b/setup.py @@ -17,66 +17,65 @@ def has_environment_marker_support(): * https://www.python.org/dev/peps/pep-0426/#environment-markers """ import pkg_resources + try: v = pkg_resources.parse_version(setuptools.__version__) - return v >= pkg_resources.parse_version('0.7.2') + return v >= pkg_resources.parse_version("0.7.2") except Exception as e: - sys.stderr.write("Could not test setuptool's version: %s\n" % e) + sys.stderr.write("Could not test setuptool's version: {}\n".format(e)) return False def get_long_description(): - with io.open('README.rst', encoding='utf-8') as f: - with io.open('CHANGELOG.rst', encoding='utf-8') as g: - return "%s\n\n%s" % (f.read(), g.read()) + with io.open("README.rst", encoding="utf-8") as f: + with io.open("CHANGELOG.rst", encoding="utf-8") as g: + return u"{}\n\n{}".format(f.read(), g.read()) def main(): setuptools.setup( - name='tox', - description='virtualenv-based automation of test activities', + name="tox", + description="virtualenv-based automation of test activities", long_description=get_long_description(), - url='https://tox.readthedocs.org/', + url="https://tox.readthedocs.org/", use_scm_version=True, - license='http://opensource.org/licenses/MIT', - platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], - author='holger krekel', - author_email='holger@merlinux.eu', - packages=['tox'], - entry_points={'console_scripts': ['tox=tox:cmdline', - 'tox-quickstart=tox._quickstart:main']}, - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', - setup_requires=['setuptools_scm'], - install_requires=['py>=1.4.17', - 'pluggy>=0.3.0,<1.0', - 'six', - 'virtualenv>=1.11.2'], - extras_require={'testing': ['pytest >= 3.0.0', - 'pytest-cov', - 'pytest-mock', - 'pytest-timeout', - 'pytest-xdist'], - 'docs': ['sphinx >= 1.6.3, < 2', - 'towncrier >= 17.8.0'], - 'lint': ['flake8 == 3.4.1', - 'flake8-bugbear == 17.4.0', - 'pre-commit == 1.4.4'], - 'publish': ['devpi', - 'twine']}, - classifiers=['Development Status :: 5 - Production/Stable', - 'Framework :: tox', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Topic :: Software Development :: Testing', - 'Topic :: Software Development :: Libraries', - 'Topic :: Utilities'] + [ - ('Programming Language :: Python :: {}'.format(x)) for - x in '2 2.7 3 3.4 3.5 3.6'.split()] + license="http://opensource.org/licenses/MIT", + platforms=["unix", "linux", "osx", "cygwin", "win32"], + author="holger krekel", + author_email="holger@merlinux.eu", + packages=["tox"], + entry_points={ + "console_scripts": ["tox=tox:cmdline", "tox-quickstart=tox._quickstart:main"] + }, + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + setup_requires=["setuptools_scm"], + install_requires=["py>=1.4.17", "pluggy>=0.3.0,<1.0", "six", "virtualenv>=1.11.2"], + extras_require={ + "testing": [ + "pytest >= 3.0.0", "pytest-cov", "pytest-mock", "pytest-timeout", "pytest-xdist" + ], + "docs": ["sphinx >= 1.6.3, < 2", "towncrier >= 17.8.0"], + "lint": ["flake8 == 3.5.0", "flake8-bugbear == 18.2.0", "pre-commit == 1.8.2"], + "publish": ["devpi", "twine"], + }, + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Framework :: tox", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS :: MacOS X", + "Topic :: Software Development :: Testing", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", + ] + + [ + ("Programming Language :: Python :: {}".format(x)) + for x in "2 2.7 3 3.4 3.5 3.6".split() + ], ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/tasks/pre-process-changelog.py b/tasks/pre-process-changelog.py index 81bcd61a4..f77ac1705 100644 --- a/tasks/pre-process-changelog.py +++ b/tasks/pre-process-changelog.py @@ -17,28 +17,28 @@ def include_draft_newsfragments(): current_path = os.getcwd() try: os.chdir(project_root) - cmd = ['towncrier', '--draft', '--dir', project_root] - out = subprocess.check_output(cmd).decode('utf-8').strip() - docs_build_dir = project_root / '.tox' / 'docs' / 'fragments.rst' + cmd = ["towncrier", "--draft", "--dir", project_root] + out = subprocess.check_output(cmd).decode("utf-8").strip() + docs_build_dir = project_root / ".tox" / "docs" / "fragments.rst" docs_build_dir.write(out) finally: os.chdir(current_path) def manipulate_the_news(): - home = 'https://github.com' - issue = '%s/issue' % home - fragmentsPath = Path(__file__).parents[1] / 'tox' / 'changelog' + home = "https://github.com" + issue = "{}/issue".format(home) + fragmentsPath = Path(__file__).parents[1] / "tox" / "changelog" for pattern, replacement in ( - (r'[^`]@([^,\s]+)', r'`@\1 <%s/\1>`_' % home), - (r'[^`]#([\d]+)', r'`#pr\1 <%s/\1>`_' % issue), + (r"[^`]@([^,\s]+)", r"`@\1 <{}/\1>`_".format(home)), + (r"[^`]#([\d]+)", r"`#pr\1 <{}/\1>`_".format(issue)), ): - for path in fragmentsPath.glob('*.rst'): + for path in fragmentsPath.glob("*.rst"): path.write_text(re.sub(pattern, replacement, path.read_text())) main = manipulate_the_news -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/tests/conftest.py b/tests/conftest.py index c30387771..ec59f4a1c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ # FIXME this seems unnecessary # TODO move fixtures here and only keep helper functions/classes in the plugin # TODO _pytest_helpers might be a better name than _pytestplugin then? +# noinspection PyUnresolvedReferences from tox._pytestplugin import * # noqa diff --git a/tests/test_config.py b/tests/test_config.py index 43f323ba8..a81a7ca1f 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -9,26 +9,39 @@ import tox from tox.config import ( - CommandParser, DepOption, SectionReader, get_homedir, get_version_info, - getcontextname, is_section_substitution, parseconfig) + CommandParser, + DepOption, + SectionReader, + get_homedir, + get_version_info, + getcontextname, + is_section_substitution, + parseconfig, +) from tox.venv import VirtualEnv class TestVenvConfig: + def test_config_parsing_minimal(self, tmpdir, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:py1] - """) + """, + ) assert len(config.envconfigs) == 1 assert config.toxworkdir.realpath() == tmpdir.join(".tox").realpath() - assert config.envconfigs['py1'].basepython == sys.executable - assert config.envconfigs['py1'].deps == [] - assert config.envconfigs['py1'].platform == ".*" + assert config.envconfigs["py1"].basepython == sys.executable + assert config.envconfigs["py1"].deps == [] + assert config.envconfigs["py1"].platform == ".*" def test_config_parsing_multienv(self, tmpdir, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [tox] - toxworkdir = %s + toxworkdir = {} indexserver = xyz = xyz_repo [testenv:py1] @@ -37,43 +50,54 @@ def test_config_parsing_multienv(self, tmpdir, newconfig): deps= world1 :xyz:http://hello/world - """ % (tmpdir,)) + """.format( + tmpdir + ), + ) assert config.toxworkdir == tmpdir assert len(config.envconfigs) == 2 - assert config.envconfigs['py1'].envdir == tmpdir.join("py1") - dep = config.envconfigs['py1'].deps[0] + assert config.envconfigs["py1"].envdir == tmpdir.join("py1") + dep = config.envconfigs["py1"].deps[0] assert dep.name == "hello" assert dep.indexserver is None - assert config.envconfigs['py2'].envdir == tmpdir.join("py2") - dep1, dep2 = config.envconfigs['py2'].deps + assert config.envconfigs["py2"].envdir == tmpdir.join("py2") + dep1, dep2 = config.envconfigs["py2"].deps assert dep1.name == "world1" assert dep2.name == "http://hello/world" assert dep2.indexserver.name == "xyz" assert dep2.indexserver.url == "xyz_repo" def test_envdir_set_manually(self, tmpdir, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:devenv] envdir = devenv - """) - envconfig = config.envconfigs['devenv'] - assert envconfig.envdir == tmpdir.join('devenv') + """, + ) + envconfig = config.envconfigs["devenv"] + assert envconfig.envdir == tmpdir.join("devenv") def test_envdir_set_manually_with_substitutions(self, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:devenv] envdir = {toxworkdir}/foobar - """) - envconfig = config.envconfigs['devenv'] - assert envconfig.envdir == config.toxworkdir.join('foobar') + """, + ) + envconfig = config.envconfigs["devenv"] + assert envconfig.envdir == config.toxworkdir.join("foobar") def test_force_dep_version(self, initproj): """ Make sure we can override dependencies configured in tox.ini when using the command line option --force-dep. """ - initproj("example123-0.5", filedefs={ - 'tox.ini': ''' + initproj( + "example123-0.5", + filedefs={ + "tox.ini": """ [tox] [testenv] @@ -82,39 +106,39 @@ def test_force_dep_version(self, initproj): dep2>=2.0 dep3 dep4==4.0 - ''' - }) + """ + }, + ) config = parseconfig( - ['--force-dep=dep1==1.5', '--force-dep=dep2==2.1', - '--force-dep=dep3==3.0']) - assert config.option.force_dep == [ - 'dep1==1.5', 'dep2==2.1', 'dep3==3.0'] - assert [str(x) for x in config.envconfigs['python'].deps] == [ - 'dep1==1.5', 'dep2==2.1', 'dep3==3.0', 'dep4==4.0' - ] + ["--force-dep=dep1==1.5", "--force-dep=dep2==2.1", "--force-dep=dep3==3.0"] + ) + assert config.option.force_dep == ["dep1==1.5", "dep2==2.1", "dep3==3.0"] + expected_deps = ["dep1==1.5", "dep2==2.1", "dep3==3.0", "dep4==4.0"] + assert expected_deps == [str(x) for x in config.envconfigs["python"].deps] def test_force_dep_with_url(self, initproj): - initproj("example123-0.5", filedefs={ - 'tox.ini': ''' + initproj( + "example123-0.5", + filedefs={ + "tox.ini": """ [tox] [testenv] deps= dep1==1.0 https://pypi.org/xyz/pkg1.tar.gz - ''' - }) - config = parseconfig( - ['--force-dep=dep1==1.5']) - assert config.option.force_dep == [ - 'dep1==1.5' - ] - assert [str(x) for x in config.envconfigs['python'].deps] == [ - 'dep1==1.5', 'https://pypi.org/xyz/pkg1.tar.gz' - ] + """ + }, + ) + config = parseconfig(["--force-dep=dep1==1.5"]) + assert config.option.force_dep == ["dep1==1.5"] + expected_deps = ["dep1==1.5", "https://pypi.org/xyz/pkg1.tar.gz"] + assert [str(x) for x in config.envconfigs["python"].deps] == expected_deps def test_process_deps(self, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv] deps = -r requirements.txt @@ -123,41 +147,53 @@ def test_process_deps(self, newconfig): --global-option=foo -v dep1 --help dep2 - """) # note that those last two are invalid - assert [str(x) for x in config.envconfigs['python'].deps] == [ - '-rrequirements.txt', '--index-url=https://pypi.org/simple', - '-fhttps://pypi.org/packages', '--global-option=foo', - '-v dep1', '--help dep2' + """, + ) # note that those last two are invalid + expected_deps = [ + "-rrequirements.txt", + "--index-url=https://pypi.org/simple", + "-fhttps://pypi.org/packages", + "--global-option=foo", + "-v dep1", + "--help dep2", ] + assert [str(x) for x in config.envconfigs["python"].deps] == expected_deps def test_is_same_dep(self): """ Ensure correct parseini._is_same_dep is working with a few samples. """ - assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3') - assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>=2.0') - assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>2.0') - assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<2.0') - assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<=2.0') - assert not DepOption._is_same_dep('pkg_hello-world3==1.0', 'otherpkg>=2.0') + assert DepOption._is_same_dep("pkg_hello-world3==1.0", "pkg_hello-world3") + assert DepOption._is_same_dep("pkg_hello-world3==1.0", "pkg_hello-world3>=2.0") + assert DepOption._is_same_dep("pkg_hello-world3==1.0", "pkg_hello-world3>2.0") + assert DepOption._is_same_dep("pkg_hello-world3==1.0", "pkg_hello-world3<2.0") + assert DepOption._is_same_dep("pkg_hello-world3==1.0", "pkg_hello-world3<=2.0") + assert not DepOption._is_same_dep("pkg_hello-world3==1.0", "otherpkg>=2.0") class TestConfigPlatform: + def test_config_parse_platform(self, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:py1] platform = linux2 - """) + """, + ) assert len(config.envconfigs) == 1 - assert config.envconfigs['py1'].platform == "linux2" + assert config.envconfigs["py1"].platform == "linux2" def test_config_parse_platform_rex(self, newconfig, mocksession, monkeypatch): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:py1] platform = a123|b123 - """) + """, + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['py1'] + envconfig = config.envconfigs["py1"] venv = VirtualEnv(envconfig, session=mocksession) assert not venv.matching_platform() monkeypatch.setattr(sys, "platform", "a123") @@ -169,26 +205,30 @@ def test_config_parse_platform_rex(self, newconfig, mocksession, monkeypatch): @pytest.mark.parametrize("plat", ["win", "lin", "osx"]) def test_config_parse_platform_with_factors(self, newconfig, plat): - config = newconfig([], """ + config = newconfig( + [], + """ [tox] envlist = py27-{win, lin,osx } [testenv] platform = win: win32 lin: linux2 - """) + """, + ) assert len(config.envconfigs) == 3 - platform = config.envconfigs['py27-' + plat].platform + platform = config.envconfigs["py27-" + plat].platform expected = {"win": "win32", "lin": "linux2", "osx": ""}.get(plat) assert platform == expected class TestConfigPackage: + def test_defaults(self, tmpdir, newconfig): config = newconfig([], "") assert config.setupdir.realpath() == tmpdir.realpath() assert config.toxworkdir.realpath() == tmpdir.join(".tox").realpath() - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] assert envconfig.args_are_paths assert not envconfig.recreate assert not envconfig.pip_pre @@ -204,14 +244,19 @@ def test_defaults_changed_dir(self, tmpdir, newconfig): assert config.toxworkdir.realpath() == tmpdir.join(".tox").realpath() def test_project_paths(self, tmpdir, newconfig): - config = newconfig(""" + config = newconfig( + """ [tox] - toxworkdir=%s - """ % tmpdir) + toxworkdir={} + """.format( + tmpdir + ) + ) assert config.toxworkdir == tmpdir class TestParseconfig: + def test_search_parents(self, tmpdir): b = tmpdir.mkdir("a").mkdir("b") toxinipath = tmpdir.ensure("tox.ini") @@ -226,31 +271,29 @@ def test_explicit_config_path(self, tmpdir): """ Test explicitly setting config path, both with and without the filename """ - path = tmpdir.mkdir('tox_tmp_directory') - config_file_path = path.ensure('tox.ini') + path = tmpdir.mkdir("tox_tmp_directory") + config_file_path = path.ensure("tox.ini") - config = parseconfig(['-c', str(config_file_path)]) + config = parseconfig(["-c", str(config_file_path)]) assert config.toxinipath == config_file_path # Passing directory of the config file should also be possible # ('tox.ini' filename is assumed) - config = parseconfig(['-c', str(path)]) + config = parseconfig(["-c", str(path)]) assert config.toxinipath == config_file_path def test_get_homedir(monkeypatch): - monkeypatch.setattr(py.path.local, "_gethomedir", - classmethod(lambda x: {}[1])) + monkeypatch.setattr(py.path.local, "_gethomedir", classmethod(lambda x: {}[1])) assert not get_homedir() - monkeypatch.setattr(py.path.local, "_gethomedir", - classmethod(lambda x: 0 / 0)) + monkeypatch.setattr(py.path.local, "_gethomedir", classmethod(lambda x: 0 / 0)) assert not get_homedir() - monkeypatch.setattr(py.path.local, "_gethomedir", - classmethod(lambda x: "123")) + monkeypatch.setattr(py.path.local, "_gethomedir", classmethod(lambda x: "123")) assert get_homedir() == "123" class TestGetcontextname: + def test_blank(self, monkeypatch): monkeypatch.setattr(os, "environ", {}) assert getcontextname() is None @@ -268,13 +311,15 @@ class TestIniParserAgainstCommandsKey: """Test parsing commands with substitutions""" def test_command_substitution_from_other_section(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key = whatever [testenv] commands = echo {[section]key} - """) + """ + ) reader = SectionReader("testenv", config._cfg) x = reader.getargvlist("commands") assert x == [["echo", "whatever"]] @@ -282,7 +327,8 @@ def test_command_substitution_from_other_section(self, newconfig): def test_command_substitution_from_other_section_multiline(self, newconfig): """Ensure referenced multiline commands form from other section injected as multiple commands.""" - config = newconfig(""" + config = newconfig( + """ [section] commands = cmd1 param11 param12 @@ -299,70 +345,81 @@ def test_command_substitution_from_other_section_multiline(self, newconfig): {[section]commands} # comment is omitted echo {[base]commands} - """) + """ + ) reader = SectionReader("testenv", config._cfg) x = reader.getargvlist("commands") - assert x == [ + expected_deps = [ "cmd1 param11 param12".split(), "cmd2 param21 param22".split(), "cmd1 param11 param12".split(), "cmd2 param21 param22".split(), - ["echo", "cmd", "1", "2", "3", "4", "cmd", "2"] + ["echo", "cmd", "1", "2", "3", "4", "cmd", "2"], ] + assert x == expected_deps def test_command_substitution_from_other_section_posargs(self, newconfig): """Ensure subsitition from other section with posargs succeeds""" - config = newconfig(""" + config = newconfig( + """ [section] key = thing {posargs} arg2 [testenv] commands = {[section]key} - """) + """ + ) reader = SectionReader("testenv", config._cfg) reader.addsubstitutions([r"argpos"]) x = reader.getargvlist("commands") - assert x == [['thing', 'argpos', 'arg2']] + assert x == [["thing", "argpos", "arg2"]] def test_command_section_and_posargs_substitution(self, newconfig): """Ensure subsitition from other section with posargs succeeds""" - config = newconfig(""" + config = newconfig( + """ [section] key = thing arg1 [testenv] commands = {[section]key} {posargs} endarg - """) + """ + ) reader = SectionReader("testenv", config._cfg) reader.addsubstitutions([r"argpos"]) x = reader.getargvlist("commands") - assert x == [['thing', 'arg1', 'argpos', 'endarg']] + assert x == [["thing", "arg1", "argpos", "endarg"]] def test_command_env_substitution(self, newconfig): """Ensure referenced {env:key:default} values are substituted correctly.""" - config = newconfig(""" + config = newconfig( + """ [testenv:py27] setenv = TEST=testvalue commands = ls {env:TEST} - """) + """ + ) envconfig = config.envconfigs["py27"] assert envconfig.commands == [["ls", "testvalue"]] assert envconfig.setenv["TEST"] == "testvalue" def test_command_env_substitution_global(self, newconfig): """Ensure referenced {env:key:default} values are substituted correctly.""" - config = newconfig(""" + config = newconfig( + """ [testenv] setenv = FOO = bar commands = echo {env:FOO} - """) - envconfig = config.envconfigs['python'] + """ + ) + envconfig = config.envconfigs["python"] assert envconfig.commands == [["echo", "bar"]] def test_regression_issue595(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [tox] envlist = foo [testenv] @@ -371,18 +428,22 @@ def test_regression_issue595(self, newconfig): setenv = {[testenv]setenv} [testenv:baz] setenv = - """) - assert config.envconfigs['foo'].setenv['VAR'] == 'x' - assert config.envconfigs['bar'].setenv['VAR'] == 'x' - assert 'VAR' not in config.envconfigs['baz'].setenv + """ + ) + assert config.envconfigs["foo"].setenv["VAR"] == "x" + assert config.envconfigs["bar"].setenv["VAR"] == "x" + assert "VAR" not in config.envconfigs["baz"].setenv class TestIniParser: + def test_getstring_single(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key=value - """) + """ + ) reader = SectionReader("section", config._cfg) x = reader.getstring("key") assert x == "value" @@ -391,23 +452,27 @@ def test_getstring_single(self, newconfig): assert x == "world" def test_missing_substitution(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [mydefault] key2={xyz} - """) - reader = SectionReader("mydefault", config._cfg, fallbacksections=['mydefault']) + """ + ) + reader = SectionReader("mydefault", config._cfg, fallbacksections=["mydefault"]) assert reader is not None with pytest.raises(tox.exception.ConfigError): reader.getstring("key2") def test_getstring_fallback_sections(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [mydefault] key2=value2 [section] key=value - """) - reader = SectionReader("section", config._cfg, fallbacksections=['mydefault']) + """ + ) + reader = SectionReader("section", config._cfg, fallbacksections=["mydefault"]) x = reader.getstring("key2") assert x == "value2" x = reader.getstring("key3") @@ -416,13 +481,15 @@ def test_getstring_fallback_sections(self, newconfig): assert x == "world" def test_getstring_substitution(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [mydefault] key2={value2} [section] key={value} - """) - reader = SectionReader("section", config._cfg, fallbacksections=['mydefault']) + """ + ) + reader = SectionReader("section", config._cfg, fallbacksections=["mydefault"]) reader.addsubstitutions(value="newvalue", value2="newvalue2") x = reader.getstring("key2") assert x == "newvalue2" @@ -432,31 +499,35 @@ def test_getstring_substitution(self, newconfig): assert x == "newvalue2" def test_getlist(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key2= item1 {item2} - """) + """ + ) reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="not", item2="grr") x = reader.getlist("key2") - assert x == ['item1', 'grr'] + assert x == ["item1", "grr"] def test_getdict(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key2= key1=item1 key2={item2} - """) + """ + ) reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="not", item2="grr") x = reader.getdict("key2") - assert 'key1' in x - assert 'key2' in x - assert x['key1'] == 'item1' - assert x['key2'] == 'grr' + assert "key1" in x + assert "key2" in x + assert x["key1"] == "item1" + assert x["key2"] == "grr" x = reader.getdict("key3", {1: 2}) assert x == {1: 2} @@ -474,16 +545,18 @@ def test_missing_env_sub_raises_config_error_in_non_testenv(self, newconfig): def test_missing_env_sub_populates_missing_subs(self, newconfig): config = newconfig("[testenv:foo]\ncommands={env:VAR}") print(SectionReader("section", config._cfg).getstring("commands")) - assert config.envconfigs['foo'].missing_subs == ['VAR'] + assert config.envconfigs["foo"].missing_subs == ["VAR"] def test_getstring_environment_substitution_with_default(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") - config = newconfig(""" + config = newconfig( + """ [section] key1={env:KEY1:DEFAULT_VALUE} key2={env:KEY2:DEFAULT_VALUE} key3={env:KEY3:} - """) + """ + ) reader = SectionReader("section", config._cfg) x = reader.getstring("key1") assert x == "hello" @@ -501,164 +574,188 @@ def test_value_doesn_match_section_substitution(self): assert is_section_substitution("{[setup] commands}") is None def test_getstring_other_section_substitution(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key = rue [testenv] key = t{[section]key} - """) + """ + ) reader = SectionReader("testenv", config._cfg) x = reader.getstring("key") assert x == "true" def test_argvlist(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key2= cmd1 {item1} {item2} cmd2 {item2} - """) + """ + ) reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="with space", item2="grr") - assert reader.getargvlist('key1') == [] + assert reader.getargvlist("key1") == [] x = reader.getargvlist("key2") - assert x == [["cmd1", "with", "space", "grr"], - ["cmd2", "grr"]] + assert x == [["cmd1", "with", "space", "grr"], ["cmd2", "grr"]] def test_argvlist_windows_escaping(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] comm = pytest {posargs} - """) + """ + ) reader = SectionReader("section", config._cfg) reader.addsubstitutions([r"hello\this"]) argv = reader.getargv("comm") assert argv == ["pytest", "hello\\this"] def test_argvlist_multiline(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key2= cmd1 {item1} \ {item2} - """) + """ + ) reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="with space", item2="grr") - assert reader.getargvlist('key1') == [] + assert reader.getargvlist("key1") == [] x = reader.getargvlist("key2") assert x == [["cmd1", "with", "space", "grr"]] def test_argvlist_quoting_in_command(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key1= cmd1 'part one' \ 'part two' - """) + """ + ) reader = SectionReader("section", config._cfg) x = reader.getargvlist("key1") assert x == [["cmd1", "part one", "part two"]] def test_argvlist_comment_after_command(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key1= cmd1 --flag # run the flag on the command - """) + """ + ) reader = SectionReader("section", config._cfg) x = reader.getargvlist("key1") assert x == [["cmd1", "--flag"]] def test_argvlist_command_contains_hash(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key1= cmd1 --re "use the # symbol for an arg" - """) + """ + ) reader = SectionReader("section", config._cfg) x = reader.getargvlist("key1") assert x == [["cmd1", "--re", "use the # symbol for an arg"]] def test_argvlist_positional_substitution(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key2= cmd1 [] cmd2 {posargs:{item2} \ other} - """) + """ + ) reader = SectionReader("section", config._cfg) - posargs = ['hello', 'world'] + posargs = ["hello", "world"] reader.addsubstitutions(posargs, item2="value2") - assert reader.getargvlist('key1') == [] + assert reader.getargvlist("key1") == [] argvlist = reader.getargvlist("key2") assert argvlist[0] == ["cmd1"] + posargs assert argvlist[1] == ["cmd2"] + posargs reader = SectionReader("section", config._cfg) reader.addsubstitutions([], item2="value2") - assert reader.getargvlist('key1') == [] + assert reader.getargvlist("key1") == [] argvlist = reader.getargvlist("key2") assert argvlist[0] == ["cmd1"] assert argvlist[1] == ["cmd2", "value2", "other"] def test_argvlist_quoted_posargs(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key2= cmd1 --foo-args='{posargs}' cmd2 -f '{posargs}' cmd3 -f {posargs} - """) + """ + ) reader = SectionReader("section", config._cfg) reader.addsubstitutions(["foo", "bar"]) - assert reader.getargvlist('key1') == [] + assert reader.getargvlist("key1") == [] x = reader.getargvlist("key2") - assert x == [["cmd1", "--foo-args=foo bar"], - ["cmd2", "-f", "foo bar"], - ["cmd3", "-f", "foo", "bar"]] + expected_deps = [ + ["cmd1", "--foo-args=foo bar"], ["cmd2", "-f", "foo bar"], ["cmd3", "-f", "foo", "bar"] + ] + assert x == expected_deps def test_argvlist_posargs_with_quotes(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key2= cmd1 -f {posargs} - """) + """ + ) reader = SectionReader("section", config._cfg) reader.addsubstitutions(["foo", "'bar", "baz'"]) - assert reader.getargvlist('key1') == [] + assert reader.getargvlist("key1") == [] x = reader.getargvlist("key2") assert x == [["cmd1", "-f", "foo", "bar baz"]] def test_positional_arguments_are_only_replaced_when_standing_alone(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key= cmd0 [] cmd1 -m '[abc]' cmd2 -m '\'something\'' [] cmd3 something[]else - """) + """ + ) reader = SectionReader("section", config._cfg) - posargs = ['hello', 'world'] + posargs = ["hello", "world"] reader.addsubstitutions(posargs) - argvlist = reader.getargvlist('key') - assert argvlist[0] == ['cmd0'] + posargs - assert argvlist[1] == ['cmd1', '-m', '[abc]'] - assert argvlist[2] == ['cmd2', '-m', "something"] + posargs - assert argvlist[3] == ['cmd3', 'something[]else'] + argvlist = reader.getargvlist("key") + assert argvlist[0] == ["cmd0"] + posargs + assert argvlist[1] == ["cmd1", "-m", "[abc]"] + assert argvlist[2] == ["cmd2", "-m", "something"] + posargs + assert argvlist[3] == ["cmd3", "something[]else"] def test_posargs_are_added_escaped_issue310(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key= cmd0 {posargs} - """) + """ + ) reader = SectionReader("section", config._cfg) - posargs = ['hello world', '--x==y z', '--format=%(code)s: %(text)s'] + posargs = ["hello world", "--x==y z", "--format=%(code)s: %(text)s"] reader.addsubstitutions(posargs) - argvlist = reader.getargvlist('key') - assert argvlist[0] == ['cmd0'] + posargs + argvlist = reader.getargvlist("key") + assert argvlist[0] == ["cmd0"] + posargs def test_substitution_with_multiple_words(self, newconfig): inisource = """ @@ -667,42 +764,46 @@ def test_substitution_with_multiple_words(self, newconfig): """ config = newconfig(inisource) reader = SectionReader("section", config._cfg) - posargs = ['hello', 'world'] - reader.addsubstitutions(posargs, envlogdir='ENV_LOG_DIR', envname='ENV_NAME') + posargs = ["hello", "world"] + reader.addsubstitutions(posargs, envlogdir="ENV_LOG_DIR", envname="ENV_NAME") - expected = [ - 'pytest', '-n5', '--junitxml=ENV_LOG_DIR/junit-ENV_NAME.xml', 'hello', 'world' - ] - assert reader.getargvlist('key')[0] == expected + expected = ["pytest", "-n5", "--junitxml=ENV_LOG_DIR/junit-ENV_NAME.xml", "hello", "world"] + assert reader.getargvlist("key")[0] == expected def test_getargv(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key=some command "with quoting" - """) + """ + ) reader = SectionReader("section", config._cfg) - expected = ['some', 'command', 'with quoting'] - assert reader.getargv('key') == expected + expected = ["some", "command", "with quoting"] + assert reader.getargv("key") == expected def test_getpath(self, tmpdir, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] path1={HELLO} - """) + """ + ) reader = SectionReader("section", config._cfg) reader.addsubstitutions(toxinidir=tmpdir, HELLO="mypath") x = reader.getpath("path1", tmpdir) assert x == tmpdir.join("mypath") def test_getbool(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [section] key1=True key2=False key1a=true key2a=falsE key5=yes - """) + """ + ) reader = SectionReader("section", config._cfg) assert reader.getbool("key1") is True assert reader.getbool("key1a") is True @@ -715,11 +816,14 @@ def test_getbool(self, newconfig): class TestIniParserPrefix: + def test_basic_section_access(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [p:section] key=value - """) + """ + ) reader = SectionReader("section", config._cfg, prefix="p") x = reader.getstring("key") assert x == "value" @@ -728,14 +832,17 @@ def test_basic_section_access(self, newconfig): assert x == "world" def test_fallback_sections(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [p:mydefault] key2=value2 [p:section] key=value - """) - reader = SectionReader("section", config._cfg, prefix="p", - fallbacksections=['p:mydefault']) + """ + ) + reader = SectionReader( + "section", config._cfg, prefix="p", fallbacksections=["p:mydefault"] + ) x = reader.getstring("key2") assert x == "value2" x = reader.getstring("key3") @@ -752,45 +859,52 @@ def test_value_doesn_match_prefixed_section_substitution(self): assert is_section_substitution("{[p:setup] commands}") is None def test_other_section_substitution(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [p:section] key = rue [p:testenv] key = t{[p:section]key} - """) + """ + ) reader = SectionReader("testenv", config._cfg, prefix="p") x = reader.getstring("key") assert x == "true" class TestConfigTestEnv: + def test_commentchars_issue33(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] # hello deps = http://abc#123 commands= python -c "x ; y" - """) + """ + ) envconfig = config.envconfigs["python"] assert envconfig.deps[0].name == "http://abc#123" assert envconfig.commands[0] == ["python", "-c", "x ; y"] def test_defaults(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] commands= xyz --abc - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] assert envconfig.commands == [["xyz", "--abc"]] assert envconfig.changedir == config.setupdir assert envconfig.sitepackages is False assert envconfig.usedevelop is False assert envconfig.ignore_errors is False assert envconfig.envlogdir == envconfig.envdir.join("log") - assert list(envconfig.setenv.definitions.keys()) == ['PYTHONHASHSEED'] - hashseed = envconfig.setenv['PYTHONHASHSEED'] + assert list(envconfig.setenv.definitions.keys()) == ["PYTHONHASHSEED"] + hashseed = envconfig.setenv["PYTHONHASHSEED"] assert isinstance(hashseed, str) # The following line checks that hashseed parses to an integer. int_hashseed = int(hashseed) @@ -800,29 +914,35 @@ def test_defaults(self, newconfig): def test_sitepackages_switch(self, newconfig): config = newconfig(["--sitepackages"], "") - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] assert envconfig.sitepackages is True def test_installpkg_tops_develop(self, newconfig): - config = newconfig(["--installpkg=abc"], """ + config = newconfig( + ["--installpkg=abc"], + """ [testenv] usedevelop = True - """) + """, + ) assert not config.envconfigs["python"].usedevelop def test_specific_command_overrides(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] commands=xyz [testenv:py] commands=abc - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['py'] + envconfig = config.envconfigs["py"] assert envconfig.commands == [["abc"]] def test_whitelist_externals(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] whitelist_externals = xyz commands=xyz @@ -831,50 +951,61 @@ def test_whitelist_externals(self, newconfig): [testenv:py] whitelist_externals = xyz2 commands=abc - """) + """ + ) assert len(config.envconfigs) == 2 - envconfig = config.envconfigs['py'] + envconfig = config.envconfigs["py"] assert envconfig.commands == [["abc"]] assert envconfig.whitelist_externals == ["xyz2"] - envconfig = config.envconfigs['x'] + envconfig = config.envconfigs["x"] assert envconfig.whitelist_externals == ["xyz"] def test_changedir(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] changedir=xyz - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] assert envconfig.changedir.basename == "xyz" assert envconfig.changedir == config.toxinidir.join("xyz") def test_ignore_errors(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] ignore_errors=True - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] assert envconfig.ignore_errors is True def test_envbindir(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] basepython=python - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] assert envconfig.envpython == envconfig.envbindir.join("python") @pytest.mark.parametrize("bp", ["jython", "pypy", "pypy3"]) def test_envbindir_jython(self, newconfig, bp): - config = newconfig(""" + config = newconfig( + """ [testenv] - basepython=%s - """ % bp) + basepython={} + """.format( + bp + ) + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] # on win32 and linux virtualenv uses "bin" for pypy/jython assert envconfig.envbindir.basename == "bin" if bp == "jython": @@ -882,19 +1013,21 @@ def test_envbindir_jython(self, newconfig, bp): @pytest.mark.parametrize("plat", ["win32", "linux2"]) def test_passenv_as_multiline_list(self, newconfig, monkeypatch, plat): - monkeypatch.setattr(tox.INFO, 'IS_WIN', plat == 'win32') + monkeypatch.setattr(tox.INFO, "IS_WIN", plat == "win32") monkeypatch.setenv("A123A", "a") monkeypatch.setenv("A123B", "b") monkeypatch.setenv("BX23", "0") - config = newconfig(""" + config = newconfig( + """ [testenv] passenv = A123* # isolated comment B?23 - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] if plat == "win32": assert "PATHEXT" in envconfig.passenv assert "SYSTEMDRIVE" in envconfig.passenv @@ -918,18 +1051,20 @@ def test_passenv_as_multiline_list(self, newconfig, monkeypatch, plat): @pytest.mark.parametrize("plat", ["win32", "linux2"]) def test_passenv_as_space_separated_list(self, newconfig, monkeypatch, plat): - monkeypatch.setattr(tox.INFO, 'IS_WIN', plat == 'win32') + monkeypatch.setattr(tox.INFO, "IS_WIN", plat == "win32") monkeypatch.setenv("A123A", "a") monkeypatch.setenv("A123B", "b") monkeypatch.setenv("BX23", "0") - config = newconfig(""" + config = newconfig( + """ [testenv] passenv = # comment A123* B?23 - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] if plat == "win32": assert "PATHEXT" in envconfig.passenv assert "SYSTEMDRIVE" in envconfig.passenv @@ -953,7 +1088,8 @@ def test_passenv_with_factor(self, newconfig, monkeypatch): monkeypatch.setenv("BX23", "0") monkeypatch.setenv("CCA43", "3") monkeypatch.setenv("CB21", "4") - config = newconfig(""" + config = newconfig( + """ [tox] envlist = {x1,x2} [testenv] @@ -963,7 +1099,8 @@ def test_passenv_with_factor(self, newconfig, monkeypatch): # passed to both environments A123C x2: A123B A123D - """) + """ + ) assert len(config.envconfigs) == 2 assert "A123A" in config.envconfigs["x1"].passenv @@ -986,10 +1123,12 @@ def test_passenv_from_global_env(self, newconfig, monkeypatch): monkeypatch.setenv("A1", "a1") monkeypatch.setenv("A2", "a2") monkeypatch.setenv("TOX_TESTENV_PASSENV", "A1") - config = newconfig(""" + config = newconfig( + """ [testenv] passenv = A2 - """) + """ + ) env = config.envconfigs["python"] assert "A1" in env.passenv assert "A2" in env.passenv @@ -998,75 +1137,92 @@ def test_passenv_glob_from_global_env(self, newconfig, monkeypatch): monkeypatch.setenv("A1", "a1") monkeypatch.setenv("A2", "a2") monkeypatch.setenv("TOX_TESTENV_PASSENV", "A*") - config = newconfig(""" + config = newconfig( + """ [testenv] - """) + """ + ) env = config.envconfigs["python"] assert "A1" in env.passenv assert "A2" in env.passenv def test_changedir_override(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] changedir=xyz [testenv:python] changedir=abc basepython=python3.6 - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] assert envconfig.changedir.basename == "abc" assert envconfig.changedir == config.setupdir.join("abc") def test_install_command_setting(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] install_command=some_install {packages} - """) - envconfig = config.envconfigs['python'] - assert envconfig.install_command == [ - 'some_install', '{packages}'] + """ + ) + envconfig = config.envconfigs["python"] + assert envconfig.install_command == ["some_install", "{packages}"] def test_install_command_must_contain_packages(self, newconfig): with pytest.raises(tox.exception.ConfigError): newconfig("[testenv]\ninstall_command=pip install") def test_install_command_substitutions(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] install_command=some_install --arg={toxinidir}/foo \ {envname} {opts} {packages} - """) - envconfig = config.envconfigs['python'] - assert envconfig.install_command == [ - 'some_install', '--arg=%s/foo' % config.toxinidir, 'python', - '{opts}', '{packages}'] + """ + ) + envconfig = config.envconfigs["python"] + expected_deps = [ + "some_install", + "--arg={}/foo".format(config.toxinidir), + "python", + "{opts}", + "{packages}", + ] + assert envconfig.install_command == expected_deps def test_pip_pre(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] pip_pre=true - """) - envconfig = config.envconfigs['python'] + """ + ) + envconfig = config.envconfigs["python"] assert envconfig.pip_pre def test_pip_pre_cmdline_override(self, newconfig): config = newconfig( - ['--pre'], + ["--pre"], """ [testenv] pip_pre=false - """) - envconfig = config.envconfigs['python'] + """, + ) + envconfig = config.envconfigs["python"] assert envconfig.pip_pre def test_simple(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv:py36] basepython=python3.6 [testenv:py27] basepython=python2.7 - """) + """ + ) assert len(config.envconfigs) == 2 assert "py36" in config.envconfigs assert "py27" in config.envconfigs @@ -1076,7 +1232,8 @@ def test_substitution_error(self, newconfig): newconfig("[testenv:py27]\nbasepython={xyz}") def test_substitution_defaults(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv:py27] commands = {toxinidir} @@ -1088,8 +1245,9 @@ def test_substitution_defaults(self, newconfig): {homedir} {distshare} {envlogdir} - """) - conf = config.envconfigs['py27'] + """ + ) + conf = config.envconfigs["py27"] argv = conf.commands assert argv[0][0] == config.toxinidir assert argv[1][0] == config.toxworkdir @@ -1102,19 +1260,22 @@ def test_substitution_defaults(self, newconfig): assert argv[8][0] == conf.envlogdir def test_substitution_notfound_issue246(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv:py27] setenv = FOO={envbindir} BAR={envsitepackagesdir} - """) - conf = config.envconfigs['py27'] + """ + ) + conf = config.envconfigs["py27"] env = conf.setenv - assert 'FOO' in env - assert 'BAR' in env + assert "FOO" in env + assert "BAR" in env def test_substitution_notfound_issue515(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [tox] envlist = standard-greeting @@ -1127,16 +1288,16 @@ def test_substitution_notfound_issue515(self, newconfig): NAME commands = python -c 'print("Hello, {env:NAME}!")' - """) - conf = config.envconfigs['standard-greeting'] - assert conf.commands == [ - ['python', '-c', 'print("Hello, world!")'] - ] + """ + ) + conf = config.envconfigs["standard-greeting"] + assert conf.commands == [["python", "-c", 'print("Hello, world!")']] def test_substitution_nested_env_defaults(self, newconfig, monkeypatch): monkeypatch.setenv("IGNORE_STATIC_DEFAULT", "env") monkeypatch.setenv("IGNORE_DYNAMIC_DEFAULT", "env") - config = newconfig(""" + config = newconfig( + """ [testenv:py27] passenv = IGNORE_STATIC_DEFAULT @@ -1151,15 +1312,16 @@ def test_substitution_nested_env_defaults(self, newconfig, monkeypatch): USE_DYNAMIC_DEFAULT={env:USE_DYNAMIC_DEFAULT:{env:OTHER_VAR}+default} IGNORE_OTHER_DEFAULT={env:OTHER_VAR:{env:OTHER_VAR}+default} USE_OTHER_DEFAULT={env:NON_EXISTENT_VAR:{env:OTHER_VAR}+default} - """) - conf = config.envconfigs['py27'] + """ + ) + conf = config.envconfigs["py27"] env = conf.setenv - assert env['IGNORE_STATIC_DEFAULT'] == "env" - assert env['USE_STATIC_DEFAULT'] == "default" - assert env['IGNORE_OTHER_DEFAULT'] == "other" - assert env['USE_OTHER_DEFAULT'] == "other+default" - assert env['IGNORE_DYNAMIC_DEFAULT'] == "env" - assert env['USE_DYNAMIC_DEFAULT'] == "other+default" + assert env["IGNORE_STATIC_DEFAULT"] == "env" + assert env["USE_STATIC_DEFAULT"] == "default" + assert env["IGNORE_OTHER_DEFAULT"] == "other" + assert env["USE_OTHER_DEFAULT"] == "other+default" + assert env["IGNORE_DYNAMIC_DEFAULT"] == "env" + assert env["USE_DYNAMIC_DEFAULT"] == "other+default" def test_substitution_positional(self, newconfig): inisource = """ @@ -1170,11 +1332,11 @@ def test_substitution_positional(self, newconfig): cmd1 {posargs:hello} \ world """ - conf = newconfig([], inisource).envconfigs['py27'] + conf = newconfig([], inisource).envconfigs["py27"] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "hello", "world"] - conf = newconfig(['brave', 'new'], inisource).envconfigs['py27'] + conf = newconfig(["brave", "new"], inisource).envconfigs["py27"] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "brave", "new", "world"] @@ -1184,7 +1346,7 @@ def test_substitution_noargs_issue240(self, newconfig): [testenv] commands = echo {posargs:foo} """ - conf = newconfig([""], inisource).envconfigs['python'] + conf = newconfig([""], inisource).envconfigs["python"] argv = conf.commands assert argv[0] == ["echo"] @@ -1198,9 +1360,9 @@ def test_substitution_double(self, newconfig): commands = echo {{[params]foo2}} """ - conf = newconfig([], inisource).envconfigs['py27'] + conf = newconfig([], inisource).envconfigs["py27"] argv = conf.commands - assert argv[0] == ['echo', 'bah'] + assert argv[0] == ["echo", "bah"] def test_posargs_backslashed_or_quoted(self, newconfig): inisource = r""" @@ -1209,15 +1371,15 @@ def test_posargs_backslashed_or_quoted(self, newconfig): echo "\{posargs\}" = {posargs} echo "posargs = " "{posargs}" """ - conf = newconfig([], inisource).envconfigs['py27'] + conf = newconfig([], inisource).envconfigs["py27"] argv = conf.commands - assert argv[0] == ['echo', '{posargs}', '='] - assert argv[1] == ['echo', 'posargs = ', ""] + assert argv[0] == ["echo", "{posargs}", "="] + assert argv[1] == ["echo", "posargs = ", ""] - conf = newconfig(['dog', 'cat'], inisource).envconfigs['py27'] + conf = newconfig(["dog", "cat"], inisource).envconfigs["py27"] argv = conf.commands - assert argv[0] == ['echo', '{posargs}', '=', 'dog', 'cat'] - assert argv[1] == ['echo', 'posargs = ', 'dog cat'] + assert argv[0] == ["echo", "{posargs}", "=", "dog", "cat"] + assert argv[1] == ["echo", "posargs = ", "dog cat"] def test_rewrite_posargs(self, tmpdir, newconfig): inisource = """ @@ -1226,16 +1388,16 @@ def test_rewrite_posargs(self, tmpdir, newconfig): changedir = tests commands = cmd1 {posargs:hello} """ - conf = newconfig([], inisource).envconfigs['py27'] + conf = newconfig([], inisource).envconfigs["py27"] argv = conf.commands assert argv[0] == ["cmd1", "hello"] - conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] + conf = newconfig(["tests/hello"], inisource).envconfigs["py27"] argv = conf.commands assert argv[0] == ["cmd1", "tests/hello"] tmpdir.ensure("tests", "hello") - conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] + conf = newconfig(["tests/hello"], inisource).envconfigs["py27"] argv = conf.commands assert argv[0] == ["cmd1", "hello"] @@ -1246,29 +1408,27 @@ def test_rewrite_simple_posargs(self, tmpdir, newconfig): changedir = tests commands = cmd1 {posargs} """ - conf = newconfig([], inisource).envconfigs['py27'] + conf = newconfig([], inisource).envconfigs["py27"] argv = conf.commands assert argv[0] == ["cmd1"] - conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] + conf = newconfig(["tests/hello"], inisource).envconfigs["py27"] argv = conf.commands assert argv[0] == ["cmd1", "tests/hello"] tmpdir.ensure("tests", "hello") - conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] + conf = newconfig(["tests/hello"], inisource).envconfigs["py27"] argv = conf.commands assert argv[0] == ["cmd1", "hello"] - @pytest.mark.parametrize('envlist, deps', [ - (['py27'], ('pytest', 'pytest-cov')), - (['py27', 'py34'], ('pytest', 'py{27,34}: pytest-cov')) - ]) - def test_take_dependencies_from_other_testenv( - self, - newconfig, - envlist, - deps - ): + @pytest.mark.parametrize( + "envlist, deps", + [ + (["py27"], ("pytest", "pytest-cov")), + (["py27", "py34"], ("pytest", "py{27,34}: pytest-cov")), + ], + ) + def test_take_dependencies_from_other_testenv(self, newconfig, envlist, deps): inisource = """ [tox] envlist = {envlist} @@ -1280,15 +1440,14 @@ def test_take_dependencies_from_other_testenv( fun frob{{env:ENV_VAR:>1.0,<2.0}} """.format( - envlist=','.join(envlist), - deps='\n' + '\n'.join([' ' * 17 + d for d in deps]) + envlist=",".join(envlist), deps="\n" + "\n".join([" " * 17 + d for d in deps]) ) - conf = newconfig([], inisource).envconfigs['py27'] + conf = newconfig([], inisource).envconfigs["py27"] packages = [dep.name for dep in conf.deps] - assert packages == ['pytest', 'pytest-cov', 'fun', 'frob>1.0,<2.0'] + assert packages == ["pytest", "pytest-cov", "fun", "frob>1.0,<2.0"] # https://github.com/tox-dev/tox/issues/706 - @pytest.mark.parametrize('envlist', [['py27', 'coverage', 'other']]) + @pytest.mark.parametrize("envlist", [["py27", "coverage", "other"]]) def test_regression_test_issue_706(self, newconfig, envlist): inisource = """ [tox] @@ -1302,19 +1461,19 @@ def test_regression_test_issue_706(self, newconfig, envlist): {{[testenv]deps}} fun """.format( - envlist=','.join(envlist), + envlist=",".join(envlist) ) - conf = newconfig([], inisource).envconfigs['coverage'] + conf = newconfig([], inisource).envconfigs["coverage"] packages = [dep.name for dep in conf.deps] - assert packages == ['flake8', 'coverage'] + assert packages == ["flake8", "coverage"] - conf = newconfig([], inisource).envconfigs['other'] + conf = newconfig([], inisource).envconfigs["other"] packages = [dep.name for dep in conf.deps] - assert packages == ['flake8'] + assert packages == ["flake8"] - conf = newconfig([], inisource).envconfigs['py27'] + conf = newconfig([], inisource).envconfigs["py27"] packages = [dep.name for dep in conf.deps] - assert packages == ['flake8', 'fun'] + assert packages == ["flake8", "fun"] def test_take_dependencies_from_other_section(self, newconfig): inisource = """ @@ -1332,9 +1491,9 @@ def test_take_dependencies_from_other_section(self, newconfig): fun """ conf = newconfig([], inisource) - env = conf.envconfigs['python'] + env = conf.envconfigs["python"] packages = [dep.name for dep in env.deps] - assert packages == ['pytest', 'pytest-cov', 'mock', 'fun'] + assert packages == ["pytest", "pytest-cov", "mock", "fun"] def test_multilevel_substitution(self, newconfig): inisource = """ @@ -1357,9 +1516,9 @@ def test_multilevel_substitution(self, newconfig): fun """ conf = newconfig([], inisource) - env = conf.envconfigs['python'] + env = conf.envconfigs["python"] packages = [dep.name for dep in env.deps] - assert packages == ['pytest', 'pytest-cov', 'mock', 'fun'] + assert packages == ["pytest", "pytest-cov", "mock", "fun"] def test_recursive_substitution_cycle_fails(self, newconfig): inisource = """ @@ -1384,8 +1543,8 @@ def test_single_value_from_other_secton(self, newconfig, tmpdir): [testenv] changedir = {[common]changedir} """ - conf = newconfig([], inisource).envconfigs['python'] - assert conf.changedir.basename == 'testing' + conf = newconfig([], inisource).envconfigs["python"] + assert conf.changedir.basename == "testing" assert conf.changedir.dirpath().realpath() == tmpdir.realpath() def test_factors(self, newconfig): @@ -1406,13 +1565,13 @@ def test_factors(self, newconfig): conf = newconfig([], inisource) configs = conf.envconfigs expected = ["dep-all", "dep-a", "dep-x", "dep-!b"] - assert [dep.name for dep in configs['a-x'].deps] == expected + assert [dep.name for dep in configs["a-x"].deps] == expected expected = ["dep-all", "dep-b", "dep-!a", "dep-!x"] - assert [dep.name for dep in configs['b'].deps] == expected + assert [dep.name for dep in configs["b"].deps] == expected expected = ["dep-all", "dep-a", "dep-x", "dep-!b"] - assert [dep.name for dep in configs['a-x'].deps] == expected + assert [dep.name for dep in configs["a-x"].deps] == expected expected = ["dep-all", "dep-b", "dep-!a", "dep-!x"] - assert [dep.name for dep in configs['b'].deps] == expected + assert [dep.name for dep in configs["b"].deps] == expected def test_factor_ops(self, newconfig): inisource = """ @@ -1437,12 +1596,11 @@ def get_deps(env): return [dep.name for dep in configs[env].deps] assert get_deps("a-x") == ["dep-a-or-b", "dep-a-and-x", "dep-a-or-!x"] - assert get_deps("a-y") == ["dep-a-or-b", "dep-ab-and-y", - "dep-a-and-!x", "dep-a-or-!x", - "dep-!a-or-!x"] + expected = ["dep-a-or-b", "dep-ab-and-y", "dep-a-and-!x", "dep-a-or-!x", "dep-!a-or-!x"] + assert get_deps("a-y") == expected assert get_deps("b-x") == ["dep-a-or-b", "dep-!a-or-!x"] - assert get_deps("b-y") == ["dep-a-or-b", "dep-ab-and-y", "dep-a-or-!x", - "dep-!a-and-!x", "dep-!a-or-!x"] + expected = ["dep-a-or-b", "dep-ab-and-y", "dep-a-or-!x", "dep-!a-and-!x", "dep-!a-or-!x"] + assert get_deps("b-y") == expected def test_envconfigs_based_on_factors(self, newconfig): inisource = """ @@ -1486,7 +1644,7 @@ def test_default_factors(self, newconfig): conf = newconfig([], inisource) configs = conf.envconfigs for name, config in configs.items(): - assert config.basepython == 'python%s.%s' % (name[2], name[3]) + assert config.basepython == "python{}.{}".format(name[2], name[3]) @pytest.mark.issue188 def test_factors_in_boolean(self, newconfig): @@ -1526,7 +1684,7 @@ def test_factor_use_not_checked(self, newconfig): deps = b: test """ configs = newconfig([], inisource).envconfigs - assert set(configs.keys()) == {'py27-a', 'py27-b'} + assert set(configs.keys()) == {"py27-a", "py27-b"} @pytest.mark.issue198 def test_factors_groups_touch(self, newconfig): @@ -1539,7 +1697,7 @@ def test_factors_groups_touch(self, newconfig): a,b,x,y: dep """ configs = newconfig([], inisource).envconfigs - assert set(configs.keys()) == {'a', 'a-x', 'b', 'b-x'} + assert set(configs.keys()) == {"a", "a-x", "b", "b-x"} def test_period_in_factor(self, newconfig): inisource = """ @@ -1565,6 +1723,7 @@ def test_ignore_outcome(self, newconfig): class TestGlobalOptions: + def test_notest(self, newconfig): config = newconfig([], "") assert not config.option.notest @@ -1579,24 +1738,21 @@ def test_verbosity(self, newconfig): config = newconfig(["-vv"], "") assert config.option.verbose_level == 2 - @pytest.mark.parametrize('args, expected', [ - ([], 0), - (["-q"], 1), - (["-qq"], 2), - (["-qqq"], 3), - ]) + @pytest.mark.parametrize("args, expected", [([], 0), (["-q"], 1), (["-qq"], 2), (["-qqq"], 3)]) def test_quiet(self, args, expected, newconfig): config = newconfig(args, "") assert config.option.quiet_level == expected def test_substitution_jenkins_default(self, monkeypatch, newconfig): monkeypatch.setenv("HUDSON_URL", "xyz") - config = newconfig(""" + config = newconfig( + """ [testenv:py27] commands = {distshare} - """) - conf = config.envconfigs['py27'] + """ + ) + conf = config.envconfigs["py27"] argv = conf.commands expect_path = config.toxworkdir.join("distshare") assert argv[0][0] == expect_path @@ -1604,23 +1760,27 @@ def test_substitution_jenkins_default(self, monkeypatch, newconfig): def test_substitution_jenkins_context(self, tmpdir, monkeypatch, newconfig): monkeypatch.setenv("HUDSON_URL", "xyz") monkeypatch.setenv("WORKSPACE", tmpdir) - config = newconfig(""" + config = newconfig( + """ [tox:jenkins] distshare = {env:WORKSPACE}/hello [testenv:py27] commands = {distshare} - """) - conf = config.envconfigs['py27'] + """ + ) + conf = config.envconfigs["py27"] argv = conf.commands assert argv[0][0] == config.distshare assert config.distshare == tmpdir.join("hello") def test_sdist_specification(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [tox] sdistsrc = {distshare}/xyz.zip - """) + """ + ) assert config.sdistsrc == config.distshare.join("xyz.zip") config = newconfig([], "") assert not config.sdistsrc @@ -1645,20 +1805,20 @@ def test_env_selection(self, newconfig, monkeypatch): assert config.envlist == ["py35", "py36"] monkeypatch.setenv("TOXENV", "ALL") config = newconfig([], inisource) - assert config.envlist == ['py27', 'py35', 'py36'] + assert config.envlist == ["py27", "py35", "py36"] config = newconfig(["-eALL"], inisource) - assert config.envlist == ['py27', 'py35', 'py36'] - config = newconfig(['-espam'], inisource) + assert config.envlist == ["py27", "py35", "py36"] + config = newconfig(["-espam"], inisource) assert config.envlist == ["spam"] def test_py_venv(self, newconfig): config = newconfig(["-epy"], "") - env = config.envconfigs['py'] + env = config.envconfigs["py"] assert str(env.basepython) == sys.executable def test_correct_basepython_chosen_from_default_factors(self, newconfig): envlist = list(tox.PYTHON.DEFAULT_FACTORS.keys()) - config = newconfig([], "[tox]\nenvlist=%s" % ", ".join(envlist)) + config = newconfig([], "[tox]\nenvlist={}".format(", ".join(envlist))) assert config.envlist == envlist for name in config.envlist: basepython = config.envconfigs[name].basepython @@ -1667,9 +1827,9 @@ def test_correct_basepython_chosen_from_default_factors(self, newconfig): elif name.startswith("pypy"): assert basepython == name elif name in ("py2", "py3"): - assert basepython == 'python' + name[-1] - elif name == 'py': - assert 'python' in basepython or "pypy" in basepython + assert basepython == "python" + name[-1] + elif name == "py": + assert "python" in basepython or "pypy" in basepython else: assert name.startswith("py") assert basepython == "python{}.{}".format(name[2], name[3]) @@ -1727,7 +1887,7 @@ def test_skip_missing_interpreters_false(self, newconfig): def test_defaultenv_commandline(self, newconfig): config = newconfig(["-epy27"], "") - env = config.envconfigs['py27'] + env = config.envconfigs["py27"] assert env.basepython == "python2.7" assert not env.commands @@ -1739,14 +1899,14 @@ def test_defaultenv_partial_override(self, newconfig): commands= xyz """ config = newconfig([], inisource) - env = config.envconfigs['py27'] + env = config.envconfigs["py27"] assert env.basepython == "python2.7" - assert env.commands == [['xyz']] + assert env.commands == [["xyz"]] class TestHashseedOption: - def _get_envconfigs(self, newconfig, args=None, tox_ini=None, - make_hashseed=None): + + def _get_envconfigs(self, newconfig, args=None, tox_ini=None, make_hashseed=None): if args is None: args = [] if tox_ini is None: @@ -1754,8 +1914,9 @@ def _get_envconfigs(self, newconfig, args=None, tox_ini=None, [testenv] """ if make_hashseed is None: + def make_hashseed(): - return '123456789' + return "123456789" original_make_hashseed = tox.config.make_hashseed tox.config.make_hashseed = make_hashseed @@ -1766,37 +1927,36 @@ def 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) + 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 + 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, newconfig): - self._check_testenv(newconfig, '123456789') + self._check_testenv(newconfig, "123456789") def test_passing_integer(self, newconfig): - args = ['--hashseed', '1'] - self._check_testenv(newconfig, '1', args=args) + args = ["--hashseed", "1"] + self._check_testenv(newconfig, "1", args=args) def test_passing_string(self, newconfig): - args = ['--hashseed', 'random'] - self._check_testenv(newconfig, 'random', args=args) + args = ["--hashseed", "random"] + self._check_testenv(newconfig, "random", args=args) def test_passing_empty_string(self, newconfig): - args = ['--hashseed', ''] - self._check_testenv(newconfig, '', args=args) + args = ["--hashseed", ""] + self._check_testenv(newconfig, "", args=args) def test_passing_no_argument(self, newconfig): """Test that passing no arguments to --hashseed is not allowed.""" - args = ['--hashseed'] + args = ["--hashseed"] try: - self._check_testenv(newconfig, '', args=args) + self._check_testenv(newconfig, "", args=args) except SystemExit: e = sys.exc_info()[1] assert e.code == 2 @@ -1810,12 +1970,12 @@ def test_setenv(self, newconfig): 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) + 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, newconfig): - args = ['--hashseed', 'noset'] + args = ["--hashseed", "noset"] envconfig = self._get_envconfig(newconfig, args=args) assert not envconfig.setenv.definitions @@ -1825,8 +1985,8 @@ def test_noset_with_setenv(self, newconfig): setenv = PYTHONHASHSEED = 2 """ - args = ['--hashseed', 'noset'] - self._check_testenv(newconfig, '2', args=args, tox_ini=tox_ini) + args = ["--hashseed", "noset"] + self._check_testenv(newconfig, "2", args=args, tox_ini=tox_ini) def test_one_random_hashseed(self, newconfig): """Check that different testenvs use the same random seed.""" @@ -1843,12 +2003,11 @@ def make_hashseed(): 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') + 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') + self._check_hashseed(envconfigs["hash2"], "1002") def test_setenv_in_one_testenv(self, newconfig): """Check using setenv in one of multiple testenvs.""" @@ -1859,19 +2018,22 @@ def test_setenv_in_one_testenv(self, newconfig): [testenv:hash2] """ envconfigs = self._get_envconfigs(newconfig, tox_ini=tox_ini) - self._check_hashseed(envconfigs["hash1"], '2') - self._check_hashseed(envconfigs["hash2"], '123456789') + self._check_hashseed(envconfigs["hash1"], "2") + self._check_hashseed(envconfigs["hash2"], "123456789") class TestSetenv: + def test_getdict_lazy(self, newconfig, monkeypatch): monkeypatch.setenv("X", "2") - config = newconfig(""" + config = newconfig( + """ [testenv:X] key0 = key1 = {env:X} key2 = {env:Y:1} - """) + """ + ) envconfig = config.envconfigs["X"] val = envconfig._reader.getdict_setenv("key0") assert val["key1"] == "2" @@ -1879,12 +2041,14 @@ def test_getdict_lazy(self, newconfig, monkeypatch): def test_getdict_lazy_update(self, newconfig, monkeypatch): monkeypatch.setenv("X", "2") - config = newconfig(""" + config = newconfig( + """ [testenv:X] key0 = key1 = {env:X} key2 = {env:Y:1} - """) + """ + ) envconfig = config.envconfigs["X"] val = envconfig._reader.getdict_setenv("key0") d = {} @@ -1893,97 +2057,114 @@ def test_getdict_lazy_update(self, newconfig, monkeypatch): def test_setenv_uses_os_environ(self, newconfig, monkeypatch): monkeypatch.setenv("X", "1") - config = newconfig(""" + config = newconfig( + """ [testenv:env1] setenv = X = {env:X} - """) + """ + ) assert config.envconfigs["env1"].setenv["X"] == "1" def test_setenv_default_os_environ(self, newconfig, monkeypatch): monkeypatch.delenv("X", raising=False) - config = newconfig(""" + config = newconfig( + """ [testenv:env1] setenv = X = {env:X:2} - """) + """ + ) assert config.envconfigs["env1"].setenv["X"] == "2" def test_setenv_uses_other_setenv(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv:env1] setenv = Y = 5 X = {env:Y} - """) + """ + ) assert config.envconfigs["env1"].setenv["X"] == "5" def test_setenv_recursive_direct(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv:env1] setenv = X = {env:X:3} - """) + """ + ) assert config.envconfigs["env1"].setenv["X"] == "3" def test_setenv_overrides(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] setenv = PYTHONPATH = something ANOTHER_VAL=else - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] - assert 'PYTHONPATH' in envconfig.setenv - assert 'ANOTHER_VAL' in envconfig.setenv - assert envconfig.setenv['PYTHONPATH'] == 'something' - assert envconfig.setenv['ANOTHER_VAL'] == 'else' + envconfig = config.envconfigs["python"] + assert "PYTHONPATH" in envconfig.setenv + assert "ANOTHER_VAL" in envconfig.setenv + assert envconfig.setenv["PYTHONPATH"] == "something" + assert envconfig.setenv["ANOTHER_VAL"] == "else" def test_setenv_with_envdir_and_basepython(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] setenv = VAL = {envdir} basepython = {env:VAL} - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] - assert 'VAL' in envconfig.setenv - assert envconfig.setenv['VAL'] == envconfig.envdir + envconfig = config.envconfigs["python"] + assert "VAL" in envconfig.setenv + assert envconfig.setenv["VAL"] == envconfig.envdir assert envconfig.basepython == envconfig.envdir def test_setenv_ordering_1(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [testenv] setenv= VAL={envdir} commands=echo {env:VAL} - """) + """ + ) assert len(config.envconfigs) == 1 - envconfig = config.envconfigs['python'] - assert 'VAL' in envconfig.setenv - assert envconfig.setenv['VAL'] == envconfig.envdir + envconfig = config.envconfigs["python"] + assert "VAL" in envconfig.setenv + assert envconfig.setenv["VAL"] == envconfig.envdir assert str(envconfig.envdir) in envconfig.commands[0] def test_setenv_cross_section_subst_issue294(self, monkeypatch, newconfig): """test that we can do cross-section substitution with setenv""" - monkeypatch.delenv('TEST', raising=False) - config = newconfig(""" + monkeypatch.delenv("TEST", raising=False) + config = newconfig( + """ [section] x = NOT_TEST={env:TEST:defaultvalue} [testenv] setenv = {[section]x} - """) + """ + ) envconfig = config.envconfigs["python"] assert envconfig.setenv["NOT_TEST"] == "defaultvalue" def test_setenv_cross_section_subst_twice(self, monkeypatch, newconfig): """test that we can do cross-section substitution with setenv""" - monkeypatch.delenv('TEST', raising=False) - config = newconfig(""" + monkeypatch.delenv("TEST", raising=False) + config = newconfig( + """ [section] x = NOT_TEST={env:TEST:defaultvalue} [section1] @@ -1991,37 +2172,43 @@ def test_setenv_cross_section_subst_twice(self, monkeypatch, newconfig): [testenv] setenv = {[section1]y} - """) + """ + ) envconfig = config.envconfigs["python"] assert envconfig.setenv["NOT_TEST"] == "defaultvalue" def test_setenv_cross_section_mixed(self, monkeypatch, newconfig): """test that we can do cross-section substitution with setenv""" - monkeypatch.delenv('TEST', raising=False) - config = newconfig(""" + monkeypatch.delenv("TEST", raising=False) + config = newconfig( + """ [section] x = NOT_TEST={env:TEST:defaultvalue} [testenv] setenv = {[section]x} y = 7 - """) + """ + ) envconfig = config.envconfigs["python"] assert envconfig.setenv["NOT_TEST"] == "defaultvalue" assert envconfig.setenv["y"] == "7" class TestIndexServer: + def test_indexserver(self, newconfig): - config = newconfig(""" + config = newconfig( + """ [tox] indexserver = name1 = XYZ name2 = ABC - """) - assert config.indexserver['default'].url is None - assert config.indexserver['name1'].url == "XYZ" - assert config.indexserver['name2'].url == "ABC" + """ + ) + assert config.indexserver["default"].url is None + assert config.indexserver["name1"].url == "XYZ" + assert config.indexserver["name2"].url == "ABC" def test_parse_indexserver(self, newconfig): inisource = """ @@ -2031,14 +2218,14 @@ def test_parse_indexserver(self, newconfig): name1 = whatever """ config = newconfig([], inisource) - assert config.indexserver['default'].url == "http://pypi.somewhere.org" - assert config.indexserver['name1'].url == "whatever" - config = newconfig(['-i', 'qwe'], inisource) - assert config.indexserver['default'].url == "qwe" - assert config.indexserver['name1'].url == "whatever" - config = newconfig(['-i', 'name1=abc', '-i', 'qwe2'], inisource) - assert config.indexserver['default'].url == "qwe2" - assert config.indexserver['name1'].url == "abc" + assert config.indexserver["default"].url == "http://pypi.somewhere.org" + assert config.indexserver["name1"].url == "whatever" + config = newconfig(["-i", "qwe"], inisource) + assert config.indexserver["default"].url == "qwe" + assert config.indexserver["name1"].url == "whatever" + config = newconfig(["-i", "name1=abc", "-i", "qwe2"], inisource) + assert config.indexserver["default"].url == "qwe2" + assert config.indexserver["name1"].url == "abc" config = newconfig(["-i", "ALL=xzy"], inisource) assert len(config.indexserver) == 2 @@ -2055,20 +2242,23 @@ def test_multiple_homedir_relative_local_indexservers(self, newconfig): pypi = http://pypi.org/simple """ config = newconfig([], inisource) - expected = "file://%s/.pip/downloads/simple" % config.homedir - assert config.indexserver['default'].url == expected - assert config.indexserver['local1'].url == config.indexserver['default'].url + expected = "file://{}/.pip/downloads/simple".format(config.homedir) + assert config.indexserver["default"].url == expected + assert config.indexserver["local1"].url == config.indexserver["default"].url class TestConfigConstSubstitutions: - @pytest.mark.parametrize('pathsep', [':', ';']) + + @pytest.mark.parametrize("pathsep", [":", ";"]) def test_replace_pathsep_unix(self, monkeypatch, newconfig, pathsep): - monkeypatch.setattr('os.pathsep', pathsep) - config = newconfig(""" + monkeypatch.setattr("os.pathsep", pathsep) + config = newconfig( + """ [testenv] setenv = PATH = dira{:}dirb{:}dirc - """) + """ + ) envconfig = config.envconfigs["python"] assert envconfig.setenv["PATH"] == pathsep.join(["dira", "dirb", "dirc"]) @@ -2077,34 +2267,36 @@ def test_pathsep_regex(self): regex = tox.config.Replacer.RE_ITEM_REF match = next(regex.finditer("{:}")) mdict = match.groupdict() - assert mdict['sub_type'] is None - assert mdict['substitution_value'] == "" - assert mdict['default_value'] == "" + assert mdict["sub_type"] is None + assert mdict["substitution_value"] == "" + assert mdict["default_value"] == "" class TestParseEnv: + def test_parse_recreate(self, newconfig): inisource = "" config = newconfig([], inisource) - assert not config.envconfigs['python'].recreate - config = newconfig(['--recreate'], inisource) - assert config.envconfigs['python'].recreate - config = newconfig(['-r'], inisource) - assert config.envconfigs['python'].recreate + assert not config.envconfigs["python"].recreate + config = newconfig(["--recreate"], inisource) + assert config.envconfigs["python"].recreate + config = newconfig(["-r"], inisource) + assert config.envconfigs["python"].recreate inisource = """ [testenv:hello] recreate = True """ config = newconfig([], inisource) - assert config.envconfigs['hello'].recreate + assert config.envconfigs["hello"].recreate class TestCmdInvocation: + def test_help(self, cmd): result = cmd("-h") assert not result.ret assert not result.err - assert re.match(r'usage:.*help.*', result.out, re.DOTALL) + assert re.match(r"usage:.*help.*", result.out, re.DOTALL) def test_version_simple(self, cmd): result = cmd("--version") @@ -2112,25 +2304,26 @@ def test_version_simple(self, cmd): assert "{} imported from".format(tox.__version__) in result.out def test_version_no_plugins(self): - pm = PluginManager('fakeprject') + pm = PluginManager("fakeprject") version_info = get_version_info(pm) assert "imported from" in version_info assert "registered plugins:" not in version_info def test_version_with_normal_plugin(self, monkeypatch): + def fake_normal_plugin_distinfo(): + class MockModule: - __file__ = 'some-file' + __file__ = "some-file" class MockEggInfo: - project_name = 'some-project' - version = '1.0' + project_name = "some-project" + version = "1.0" return [(MockModule, MockEggInfo)] - pm = PluginManager('fakeproject') - monkeypatch.setattr( - pm, 'list_plugin_distinfo', fake_normal_plugin_distinfo) + pm = PluginManager("fakeproject") + monkeypatch.setattr(pm, "list_plugin_distinfo", fake_normal_plugin_distinfo) version_info = get_version_info(pm) assert "registered plugins:" in version_info assert "some-file" in version_info @@ -2138,20 +2331,22 @@ class MockEggInfo: assert "1.0" in version_info def test_version_with_fileless_module(self, monkeypatch): + def fake_no_file_plugin_distinfo(): + class MockModule: + def __repr__(self): return "some-repr" class MockEggInfo: - project_name = 'some-project' - version = '1.0' + project_name = "some-project" + version = "1.0" return [(MockModule(), MockEggInfo)] - pm = PluginManager('fakeproject') - monkeypatch.setattr( - pm, 'list_plugin_distinfo', fake_no_file_plugin_distinfo) + pm = PluginManager("fakeproject") + monkeypatch.setattr(pm, "list_plugin_distinfo", fake_no_file_plugin_distinfo) version_info = get_version_info(pm) assert "registered plugins:" in version_info assert "some-project" in version_info @@ -2159,8 +2354,10 @@ class MockEggInfo: assert "1.0" in version_info def test_listenvs(self, cmd, initproj): - initproj('listenvs', filedefs={ - 'tox.ini': ''' + initproj( + "listenvs", + filedefs={ + "tox.ini": """ [tox] envlist=py36,py27,py34,pypy,docs description= py27: run pytest on Python 2.7 @@ -2174,14 +2371,17 @@ def test_listenvs(self, cmd, initproj): [testenv:docs] changedir = docs - ''' - }) + """ + }, + ) result = cmd("-l") - assert result.outlines == ['py36', 'py27', 'py34', 'pypy', 'docs'] + assert result.outlines == ["py36", "py27", "py34", "pypy", "docs"] def test_listenvs_verbose_description(self, cmd, initproj): - initproj('listenvs_verbose_description', filedefs={ - 'tox.ini': ''' + initproj( + "listenvs_verbose_description", + filedefs={ + "tox.ini": """ [tox] envlist=py36,py27,py34,pypy,docs [testenv] @@ -2198,20 +2398,25 @@ def test_listenvs_verbose_description(self, cmd, initproj): [testenv:docs] changedir = docs description = let me overwrite that - ''' - }) + """ + }, + ) result = cmd("-lv") - assert result.outlines[2:] == [ - 'default environments:', - 'py36 -> run pytest on Python 3.6', - 'py27 -> run pytest on Python 2.7', - 'py34 -> run pytest on Python 3.4', - 'pypy -> publish to pypy', - 'docs -> let me overwrite that'] + expected = [ + "default environments:", + "py36 -> run pytest on Python 3.6", + "py27 -> run pytest on Python 2.7", + "py34 -> run pytest on Python 3.4", + "pypy -> publish to pypy", + "docs -> let me overwrite that", + ] + assert result.outlines[2:] == expected def test_listenvs_all(self, cmd, initproj): - initproj('listenvs_all', filedefs={ - 'tox.ini': ''' + initproj( + "listenvs_all", + filedefs={ + "tox.ini": """ [tox] envlist=py36,py27,py34,pypy,docs @@ -2220,14 +2425,18 @@ def test_listenvs_all(self, cmd, initproj): [testenv:docs] changedir = docs - ''' - }) + """ + }, + ) result = cmd("-a") - assert result.outlines == ['py36', 'py27', 'py34', 'pypy', 'docs', 'notincluded'] + expected = ["py36", "py27", "py34", "pypy", "docs", "notincluded"] + assert result.outlines == expected def test_listenvs_all_verbose_description(self, cmd, initproj): - initproj('listenvs_all_verbose_description', filedefs={ - 'tox.ini': ''' + initproj( + "listenvs_all_verbose_description", + filedefs={ + "tox.ini": """ [tox] envlist={py27,py36}-{windows,linux} # py35 [testenv] @@ -2240,8 +2449,9 @@ def test_listenvs_all_verbose_description(self, cmd, initproj): [testenv:docs] changedir = docs - ''', - }) + """ + }, + ) result = cmd("-av") expected = [ "default environments:", @@ -2251,45 +2461,54 @@ def test_listenvs_all_verbose_description(self, cmd, initproj): "py36-linux -> run pytest on Python 3.6 on Linux platform", "", "additional environments:", - "docs -> generate documentation"] + "docs -> generate documentation", + ] assert result.outlines[-len(expected):] == expected def test_listenvs_all_verbose_description_no_additional_environments(self, cmd, initproj): - initproj('listenvs_all_verbose_description', filedefs={ - 'tox.ini': ''' + initproj( + "listenvs_all_verbose_description", + filedefs={ + "tox.ini": """ [tox] envlist=py27,py36 - ''' - }) + """ + }, + ) result = cmd("-av") - expected = ["default environments:", - "py27 -> [no description]", - "py36 -> [no description]"] + expected = [ + "default environments:", "py27 -> [no description]", "py36 -> [no description]" + ] assert result.out.splitlines()[-3:] == expected - assert 'additional environments' not in result.out + assert "additional environments" not in result.out def test_config_specific_ini(self, tmpdir, cmd): ini = tmpdir.ensure("hello.ini") result = cmd("-c", ini, "--showconfig") assert not result.ret - assert result.outlines[1] == 'config-file: {}'.format(ini) + assert result.outlines[1] == "config-file: {}".format(ini) def test_no_tox_ini(self, cmd, initproj): - initproj("noini-0.5", ) + initproj("noini-0.5") result = cmd() assert result.ret - assert result.out == '' + assert result.out == "" assert result.err == "ERROR: toxini file 'tox.ini' not found\n" def test_override_workdir(self, cmd, initproj): baddir = "badworkdir-123" gooddir = "overridden-234" - initproj("overrideworkdir-0.5", filedefs={ - 'tox.ini': ''' + initproj( + "overrideworkdir-0.5", + filedefs={ + "tox.ini": """ [tox] - toxworkdir=%s - ''' % baddir, - }) + toxworkdir={} + """.format( + baddir + ) + }, + ) result = cmd("--workdir", gooddir, "--showconfig") assert not result.ret assert gooddir in result.out @@ -2298,46 +2517,54 @@ def test_override_workdir(self, cmd, initproj): assert not py.path.local(baddir).check() def test_showconfig_with_force_dep_version(self, cmd, initproj): - initproj('force_dep_version', filedefs={ - 'tox.ini': ''' + initproj( + "force_dep_version", + filedefs={ + "tox.ini": """ [tox] [testenv] deps= dep1==2.3 dep2 - ''', - }) + """ + }, + ) result = cmd("--showconfig") assert result.ret == 0 - assert any(re.match(r'.*deps.*dep1==2.3, dep2.*', l) for l in result.outlines) + assert any(re.match(r".*deps.*dep1==2.3, dep2.*", l) for l in result.outlines) # override dep1 specific version, and force version for dep2 - result = cmd("--showconfig", "--force-dep=dep1", - "--force-dep=dep2==5.0") + result = cmd("--showconfig", "--force-dep=dep1", "--force-dep=dep2==5.0") assert result.ret == 0 - assert any(re.match(r'.*deps.*dep1, dep2==5.0.*', l) for l in result.outlines) + assert any(re.match(r".*deps.*dep1, dep2==5.0.*", l) for l in result.outlines) - @pytest.mark.xfail("'pypy' not in sys.executable", reason='Upstream bug. See #203') + @pytest.mark.xfail("'pypy' not in sys.executable", reason="Upstream bug. See #203") def test_colon_symbol_in_directory_name(self, cmd, initproj): - initproj('colon:_symbol_in_dir_name', filedefs={ - 'tox.ini': ''' + initproj( + "colon:_symbol_in_dir_name", + filedefs={ + "tox.ini": """ [tox] envlist = py27 [testenv] commands = pip --version - ''' - }) + """ + }, + ) result = cmd() assert result.ret == 0 -@pytest.mark.parametrize("cmdline,envlist", [ - ("-e py36", ['py36']), - ("-e py36,py34", ['py36', 'py34']), - ("-e py36,py36", ['py36', 'py36']), - ("-e py36,py34 -e py34,py27", ['py36', 'py34', 'py34', 'py27']) -]) +@pytest.mark.parametrize( + "cmdline,envlist", + [ + ("-e py36", ["py36"]), + ("-e py36,py34", ["py36", "py34"]), + ("-e py36,py36", ["py36", "py36"]), + ("-e py36,py34 -e py34,py27", ["py36", "py34", "py34", "py27"]), + ], +) def test_env_spec(cmdline, envlist): args = cmdline.split() config = parseconfig(args) @@ -2345,33 +2572,64 @@ def test_env_spec(cmdline, envlist): class TestCommandParser: + def test_command_parser_for_word(self): - p = CommandParser('word') - assert list(p.words()) == ['word'] + p = CommandParser("word") + assert list(p.words()) == ["word"] def test_command_parser_for_posargs(self): - p = CommandParser('[]') - assert list(p.words()) == ['[]'] + p = CommandParser("[]") + assert list(p.words()) == ["[]"] def test_command_parser_for_multiple_words(self): - p = CommandParser('w1 w2 w3 ') - assert list(p.words()) == ['w1', ' ', 'w2', ' ', 'w3'] + p = CommandParser("w1 w2 w3 ") + assert list(p.words()) == ["w1", " ", "w2", " ", "w3"] def test_command_parser_for_substitution_with_spaces(self): - p = CommandParser('{sub:something with spaces}') - assert list(p.words()) == ['{sub:something with spaces}'] + p = CommandParser("{sub:something with spaces}") + assert list(p.words()) == ["{sub:something with spaces}"] def test_command_parser_with_complex_word_set(self): complex_case = ( - 'word [] [literal] {something} {some:other thing} w{ord} w{or}d w{ord} ' - 'w{o:rd} w{o:r}d {w:or}d w[]ord {posargs:{a key}}') + "word [] [literal] {something} {some:other thing} w{ord} w{or}d w{ord} " + "w{o:rd} w{o:r}d {w:or}d w[]ord {posargs:{a key}}" + ) p = CommandParser(complex_case) parsed = list(p.words()) expected = [ - 'word', ' ', '[]', ' ', '[literal]', ' ', '{something}', ' ', '{some:other thing}', - ' ', 'w', '{ord}', ' ', 'w', '{or}', 'd', ' ', 'w', '{ord}', ' ', 'w', '{o:rd}', ' ', - 'w', '{o:r}', 'd', ' ', '{w:or}', 'd', - ' ', 'w[]ord', ' ', '{posargs:{a key}}' + "word", + " ", + "[]", + " ", + "[literal]", + " ", + "{something}", + " ", + "{some:other thing}", + " ", + "w", + "{ord}", + " ", + "w", + "{or}", + "d", + " ", + "w", + "{ord}", + " ", + "w", + "{o:rd}", + " ", + "w", + "{o:r}", + "d", + " ", + "{w:or}", + "d", + " ", + "w[]ord", + " ", + "{posargs:{a key}}", ] assert parsed == expected @@ -2380,29 +2638,35 @@ def test_command_with_runs_of_whitespace(self): cmd = "cmd1 {item1}\n {item2}" p = CommandParser(cmd) parsed = list(p.words()) - assert parsed == ['cmd1', ' ', '{item1}', '\n ', '{item2}'] + assert parsed == ["cmd1", " ", "{item1}", "\n ", "{item2}"] def test_command_with_split_line_in_subst_arguments(self): - cmd = dedent(""" cmd2 {posargs:{item2} - other}""") + cmd = dedent( + """ cmd2 {posargs:{item2} + other}""" + ) p = CommandParser(cmd) parsed = list(p.words()) - assert parsed == ['cmd2', ' ', '{posargs:{item2}\n other}'] + expected = ["cmd2", " ", "{posargs:{item2}\n other}"] + assert parsed == expected def test_command_parsing_for_issue_10(self): cmd = "nosetests -v -a !deferred --with-doctest []" p = CommandParser(cmd) parsed = list(p.words()) - assert parsed == [ - 'nosetests', ' ', '-v', ' ', '-a', ' ', '!deferred', ' ', - '--with-doctest', ' ', '[]' + expected = [ + "nosetests", " ", "-v", " ", "-a", " ", "!deferred", " ", "--with-doctest", " ", "[]" ] + assert parsed == expected # @mark_dont_run_on_windows def test_commands_with_backslash(self, newconfig): - config = newconfig([r"hello\world"], """ + config = newconfig( + [r"hello\world"], + """ [testenv:py36] commands = some {posargs} - """) + """, + ) envconfig = config.envconfigs["py36"] assert envconfig.commands[0] == ["some", r"hello\world"] diff --git a/tests/test_interpreters.py b/tests/test_interpreters.py index 83dfab5fd..cba8e639b 100644 --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -9,9 +9,9 @@ import tox from tox._pytestplugin import mark_dont_run_on_posix from tox.config import get_plugin_manager -from tox.interpreters import ( - ExecFailed, InterpreterInfo, Interpreters, NoInterpreterInfo, pyinfo, - run_and_get_interpreter_info, sitepackagesdir, tox_get_python_executable) +from tox.interpreters import ExecFailed, InterpreterInfo, Interpreters, NoInterpreterInfo +from tox.interpreters import pyinfo, run_and_get_interpreter_info, sitepackagesdir +from tox.interpreters import tox_get_python_executable @pytest.fixture(name="interpreters") @@ -25,11 +25,11 @@ def test_locate_via_py(monkeypatch): from tox.interpreters import locate_via_py def fake_find_exe(exe): - assert exe == 'py' - return 'py' + assert exe == "py" + return "py" def fake_popen(cmd, stdout, stderr): - assert cmd[:3] == ('py', '-3.2', '-c') + assert cmd[:3] == ("py", "-3.2", "-c") # need to pipe all stdout to collect the version information & need to # do the same for stderr output to avoid it being forwarded as the @@ -47,12 +47,13 @@ def communicate(): return proc - monkeypatch.setattr(distutils.spawn, 'find_executable', fake_find_exe) - monkeypatch.setattr(subprocess, 'Popen', fake_popen) - assert locate_via_py('3', '2') == sys.executable + monkeypatch.setattr(distutils.spawn, "find_executable", fake_find_exe) + monkeypatch.setattr(subprocess, "Popen", fake_popen) + assert locate_via_py("3", "2") == sys.executable def test_tox_get_python_executable(): + class envconfig: basepython = sys.executable envname = "pyxx" @@ -60,10 +61,10 @@ class envconfig: p = tox_get_python_executable(envconfig) assert p == py.path.local(sys.executable) for major, minor in tox.PYTHON.CPYTHON_VERSION_TUPLES: - name = "python%s.%s" % (major, minor) + name = "python{}.{}".format(major, minor) if tox.INFO.IS_WIN: - pydir = "python%s%s" % (major, minor) - x = py.path.local(r"c:\%s" % pydir) + pydir = "python{}{}".format(major, minor) + x = py.path.local(r"c:\{}".format(pydir)) print(x) if not x.check(): continue @@ -73,15 +74,15 @@ class envconfig: envconfig.basepython = name p = tox_get_python_executable(envconfig) assert p - popen = subprocess.Popen([str(p), '-V'], stderr=subprocess.PIPE, - stdout=subprocess.PIPE) + popen = subprocess.Popen([str(p), "-V"], stderr=subprocess.PIPE, stdout=subprocess.PIPE) stdout, stderr = popen.communicate() assert not stdout or not stderr - all_output = stderr.decode('ascii') + stdout.decode('ascii') - assert "%s.%s" % (major, minor) in all_output + all_output = stderr.decode("ascii") + stdout.decode("ascii") + assert "{}.{}".format(major, minor) in all_output def test_find_executable_extra(monkeypatch): + @staticmethod def sysfind(_): return "hello" @@ -105,7 +106,9 @@ def test_run_and_get_interpreter_info(): class TestInterpreters: + def test_get_executable(self, interpreters): + class envconfig: basepython = sys.executable envname = "pyxx" @@ -118,6 +121,7 @@ class envconfig: assert info.runnable def test_get_executable_no_exist(self, interpreters): + class envconfig: basepython = "1lkj23" envname = "pyxx" @@ -130,6 +134,7 @@ class envconfig: assert not info.runnable def test_get_sitepackagesdir_error(self, interpreters): + class envconfig: basepython = sys.executable envname = "123" @@ -150,6 +155,7 @@ def test_pyinfo(monkeypatch): def test_sitepackagesdir(monkeypatch): import distutils.sysconfig as sysconfig + test_envdir = "holy grail" test_dir = "Now go away or I will taunt you a second time." @@ -171,9 +177,14 @@ def test_exec_failed(): class TestInterpreterInfo: + @staticmethod - def info(name="my-name", executable="my-executable", - version_info="my-version-info", sysplatform="my-sys-platform"): + def info( + name="my-name", + executable="my-executable", + version_info="my-version-info", + sysplatform="my-sys-platform", + ): return InterpreterInfo(name, executable, version_info, sysplatform) def test_runnable(self): @@ -197,6 +208,7 @@ def test_str(self): class TestNoInterpreterInfo: + def test_runnable(self): assert not NoInterpreterInfo("foo").runnable assert not NoInterpreterInfo("foo", executable=sys.executable).runnable @@ -210,8 +222,7 @@ def test_default_data(self): assert x.err == "not found" def test_set_data(self): - x = NoInterpreterInfo( - "migraine", executable="my-executable", out="my-out", err="my-err") + x = NoInterpreterInfo("migraine", executable="my-executable", out="my-out", err="my-err") assert x.name == "migraine" assert x.executable == "my-executable" assert x.version_info is None diff --git a/tests/test_pytest_plugins.py b/tests/test_pytest_plugins.py index 126aa2d01..326ba48b6 100644 --- a/tests/test_pytest_plugins.py +++ b/tests/test_pytest_plugins.py @@ -10,89 +10,97 @@ class TestInitProj: - @pytest.mark.parametrize('kwargs', ( - {}, - {'src_root': None}, - {'src_root': ''}, - {'src_root': '.'})) + + @pytest.mark.parametrize( + "kwargs", ({}, {"src_root": None}, {"src_root": ""}, {"src_root": "."}) + ) def test_no_src_root(self, kwargs, tmpdir, initproj): - initproj('black_knight-42', **kwargs) - init_file = tmpdir.join('black_knight', 'black_knight', '__init__.py') + initproj("black_knight-42", **kwargs) + init_file = tmpdir.join("black_knight", "black_knight", "__init__.py") assert init_file.read_binary() == b"__version__ = '42'" def test_existing_src_root(self, tmpdir, initproj): - initproj('spam-666', src_root='ham') - assert not tmpdir.join('spam', 'spam').check(exists=1) - init_file = tmpdir.join('spam', 'ham', 'spam', '__init__.py') + initproj("spam-666", src_root="ham") + assert not tmpdir.join("spam", "spam").check(exists=1) + init_file = tmpdir.join("spam", "ham", "spam", "__init__.py") assert init_file.read_binary() == b"__version__ = '666'" def test_prebuilt_src_dir_with_no_src_root(self, tmpdir, initproj): - initproj('spam-1.0', filedefs={'spam': {}}) - src_dir = tmpdir.join('spam', 'spam') + initproj("spam-1.0", filedefs={"spam": {}}) + src_dir = tmpdir.join("spam", "spam") assert src_dir.check(dir=1) - assert not src_dir.join('__init__.py').check(exists=1) + assert not src_dir.join("__init__.py").check(exists=1) def test_prebuilt_src_dir_with_src_root(self, tmpdir, initproj): initproj( - 'spam-1.0', - filedefs={'incontinentia': {'spam': {'__init__.py': 'buttocks'}}}, - src_root='incontinentia') - assert not tmpdir.join('spam', 'spam').check(exists=1) - init_file = tmpdir.join('spam', 'incontinentia', 'spam', '__init__.py') - assert init_file.read_binary() == b'buttocks' - - def test_broken_py_path_local_join_workaround_on_Windows( - self, tmpdir, initproj, monkeypatch): + "spam-1.0", + filedefs={"incontinentia": {"spam": {"__init__.py": "buttocks"}}}, + src_root="incontinentia", + ) + assert not tmpdir.join("spam", "spam").check(exists=1) + init_file = tmpdir.join("spam", "incontinentia", "spam", "__init__.py") + assert init_file.read_binary() == b"buttocks" + + def test_broken_py_path_local_join_workaround_on_Windows(self, tmpdir, initproj, monkeypatch): # construct an absolute folder path for our src_root folder without the # Windows drive indicator - src_root = tmpdir.join('spam') + src_root = tmpdir.join("spam") src_root = _path_parts(src_root) - src_root[0] = '' - src_root = '/'.join(src_root) + src_root[0] = "" + src_root = "/".join(src_root) # make sure tmpdir drive is the current one so the constructed src_root # folder path gets interpreted correctly on Windows monkeypatch.chdir(tmpdir) # will throw an assertion error if the bug is not worked around - initproj('spam-666', src_root=src_root) + initproj("spam-666", src_root=src_root) - init_file = tmpdir.join('spam', 'spam', '__init__.py') + init_file = tmpdir.join("spam", "spam", "__init__.py") assert init_file.read_binary() == b"__version__ = '666'" class TestPathParts: - @pytest.mark.parametrize('input, expected', ( - ('', []), - ('/', ['/']), - ('//', ['//']), - ('/a', ['/', 'a']), - ('/a/', ['/', 'a']), - ('/a/b', ['/', 'a', 'b']), - ('a', ['a']), - ('a/b', ['a', 'b']))) + + @pytest.mark.parametrize( + "input, expected", + ( + ("", []), + ("/", ["/"]), + ("//", ["//"]), + ("/a", ["/", "a"]), + ("/a/", ["/", "a"]), + ("/a/b", ["/", "a", "b"]), + ("a", ["a"]), + ("a/b", ["a", "b"]), + ), + ) def test_path_parts(self, input, expected): assert _path_parts(input) == expected def test_on_py_path(self): cwd_parts = _path_parts(py.path.local()) - folder_parts = _path_parts(py.path.local('a/b/c')) - assert folder_parts[len(cwd_parts):] == ['a', 'b', 'c'] - - -@pytest.mark.parametrize('base, filedefs, target, expected', ( - ('/base', {}, '', False), - ('/base', {}, '/base', False), - ('/base', {'a': {'b': 'data'}}, '', True), - ('/base', {'a': {'b': 'data'}}, 'a', True), - ('/base', {'a': {'b': 'data'}}, 'a/b', True), - ('/base', {'a': {'b': 'data'}}, 'a/x', False), - ('/base', {'a': {'b': 'data'}}, 'a/b/c', False), - ('/base', {'a': {'b': 'data'}}, '/base', True), - ('/base', {'a': {'b': 'data'}}, '/base/a', True), - ('/base', {'a': {'b': 'data'}}, '/base/a/b', True), - ('/base', {'a': {'b': 'data'}}, '/base/a/x', False), - ('/base', {'a': {'b': 'data'}}, '/base/a/b/c', False), - ('/base', {'a': {'b': 'data'}}, '/a', False))) + folder_parts = _path_parts(py.path.local("a/b/c")) + assert folder_parts[len(cwd_parts):] == ["a", "b", "c"] + + +@pytest.mark.parametrize( + "base, filedefs, target, expected", + ( + ("/base", {}, "", False), + ("/base", {}, "/base", False), + ("/base", {"a": {"b": "data"}}, "", True), + ("/base", {"a": {"b": "data"}}, "a", True), + ("/base", {"a": {"b": "data"}}, "a/b", True), + ("/base", {"a": {"b": "data"}}, "a/x", False), + ("/base", {"a": {"b": "data"}}, "a/b/c", False), + ("/base", {"a": {"b": "data"}}, "/base", True), + ("/base", {"a": {"b": "data"}}, "/base/a", True), + ("/base", {"a": {"b": "data"}}, "/base/a/b", True), + ("/base", {"a": {"b": "data"}}, "/base/a/x", False), + ("/base", {"a": {"b": "data"}}, "/base/a/b/c", False), + ("/base", {"a": {"b": "data"}}, "/a", False), + ), +) def test_filedefs_contains(base, filedefs, target, expected): assert bool(_filedefs_contains(base, filedefs, target)) == expected diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 666ae18fc..0c9ffff08 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -3,14 +3,14 @@ import pytest import tox -from tox._quickstart import ( - ALTERNATIVE_CONFIG_NAME, list_modificator, main, post_process_input, prepare_content, - QUICKSTART_CONF) +from tox._quickstart import ALTERNATIVE_CONFIG_NAME, list_modificator, main, post_process_input +from tox._quickstart import prepare_content, QUICKSTART_CONF -ALL_PY_ENVS_AS_STRING = ', '.join(tox.PYTHON.QUICKSTART_PY_ENVS) -ALL_PY_ENVS_WO_LAST_AS_STRING = ', '.join(tox.PYTHON.QUICKSTART_PY_ENVS[:-1]) +ALL_PY_ENVS_AS_STRING = ", ".join(tox.PYTHON.QUICKSTART_PY_ENVS) +ALL_PY_ENVS_WO_LAST_AS_STRING = ", ".join(tox.PYTHON.QUICKSTART_PY_ENVS[:-1]) SIGNS_OF_SANITY = ( - 'tox.readthedocs.io', '[tox]', '[testenv]', 'envlist = ', 'deps =', 'commands =') + "tox.readthedocs.io", "[tox]", "[testenv]", "envlist = ", "deps =", "commands =" +) """A bunch of elements to be expected in the generated config as marker for basic sanity""" @@ -27,18 +27,18 @@ def __str__(self): return "|".join(self._inputs) def __call__(self, prompt): - print("prompt: '%s'" % prompt) + print("prompt: '{}'".format(prompt)) try: answer = self._inputs.pop(0) - print("user answer: '%s'" % answer) + print("user answer: '{}'".format(answer)) return answer except IndexError: - pytest.fail("missing user answer for '%s'" % prompt) + pytest.fail("missing user answer for '{}'".format(prompt)) class _cnf: """Handle files and args for different test scenarios.""" - SOME_CONTENT = 'dontcare' + SOME_CONTENT = "dontcare" def __init__(self, exists=False, names=None, pass_path=False): self.original_name = tox.INFO.DEFAULT_CONFIG_NAME @@ -51,21 +51,21 @@ def __str__(self): @property def argv(self): - argv = ['tox-quickstart'] + argv = ["tox-quickstart"] if self.pass_path: argv.append(os.getcwd()) return argv @property def dpath(self): - return os.getcwd() if self.pass_path else '' + return os.getcwd() if self.pass_path else "" def create(self): paths_to_create = {self._original_path} for name in self.names[:-1]: paths_to_create.add(os.path.join(self.dpath, name)) for path in paths_to_create: - with open(path, 'w') as f: + with open(path, "w") as f: f.write(self.SOME_CONTENT) @property @@ -105,109 +105,130 @@ def _alternative_content(self): class _exp: """Holds test expectations and a user scenario description.""" - STANDARD_EPECTATIONS = [ALL_PY_ENVS_AS_STRING, 'pytest', 'pytest'] + STANDARD_EPECTATIONS = [ALL_PY_ENVS_AS_STRING, "pytest", "pytest"] def __init__(self, name, exp=None): self.name = name exp = exp or self.STANDARD_EPECTATIONS # NOTE extra mangling here ensures formatting is the same in file and exp - map_ = {'deps': list_modificator(exp[1]), 'commands': list_modificator(exp[2])} + map_ = {"deps": list_modificator(exp[1]), "commands": list_modificator(exp[2])} post_process_input(map_) - map_['envlist'] = exp[0] + map_["envlist"] = exp[0] self.content = prepare_content(QUICKSTART_CONF.format(**map_)) def __str__(self): return self.name -@pytest.mark.usefixtures('work_in_clean_dir') -@pytest.mark.parametrize(argnames='answers, exp, cnf', ids=lambda param: str(param), argvalues=( +@pytest.mark.usefixtures("work_in_clean_dir") +@pytest.mark.parametrize( + argnames="answers, exp, cnf", + ids=lambda param: str(param), + argvalues=( ( - _answers([4, 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'pytest', 'pytest']), - _exp('choose versions individually and use pytest', - [ALL_PY_ENVS_WO_LAST_AS_STRING, 'pytest', 'pytest']), + _answers([4, "Y", "Y", "Y", "Y", "Y", "N", "pytest", "pytest"]), + _exp( + "choose versions individually and use pytest", + [ALL_PY_ENVS_WO_LAST_AS_STRING, "pytest", "pytest"], + ), _cnf(), ), ( - _answers([4, 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'py.test', '']), - _exp('choose versions individually and use old fashioned py.test', - [ALL_PY_ENVS_WO_LAST_AS_STRING, 'pytest', 'py.test']), + _answers([4, "Y", "Y", "Y", "Y", "Y", "N", "py.test", ""]), + _exp( + "choose versions individually and use old fashioned py.test", + [ALL_PY_ENVS_WO_LAST_AS_STRING, "pytest", "py.test"], + ), _cnf(), ), ( - _answers([1, 'pytest', '']), - _exp('choose current release Python and pytest with defaut deps', - [tox.PYTHON.CURRENT_RELEASE_ENV, 'pytest', 'pytest']), + _answers([1, "pytest", ""]), + _exp( + "choose current release Python and pytest with defaut deps", + [tox.PYTHON.CURRENT_RELEASE_ENV, "pytest", "pytest"], + ), _cnf(), ), ( - _answers([1, 'pytest -n auto', 'pytest-xdist']), - _exp('choose current release Python and pytest with xdist and some args', - [tox.PYTHON.CURRENT_RELEASE_ENV, 'pytest, pytest-xdist', - 'pytest -n auto']), + _answers([1, "pytest -n auto", "pytest-xdist"]), + _exp( + "choose current release Python and pytest with xdist and some args", + [tox.PYTHON.CURRENT_RELEASE_ENV, "pytest, pytest-xdist", "pytest -n auto"], + ), _cnf(), ), ( - _answers([2, 'pytest', '']), - _exp('choose py27, current release Python and pytest with defaut deps', - ['py27, %s' % tox.PYTHON.CURRENT_RELEASE_ENV, 'pytest', 'pytest']), + _answers([2, "pytest", ""]), + _exp( + "choose py27, current release Python and pytest with defaut deps", + ["py27, {}".format(tox.PYTHON.CURRENT_RELEASE_ENV), "pytest", "pytest"], + ), _cnf(), ), ( - _answers([3, 'pytest', '']), - _exp('choose all supported version and pytest with defaut deps'), + _answers([3, "pytest", ""]), + _exp("choose all supported version and pytest with defaut deps"), _cnf(), ), ( - _answers([4, 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'py.test', '']), - _exp('choose versions individually and use old fashioned py.test', - [ALL_PY_ENVS_WO_LAST_AS_STRING, 'pytest', 'py.test']), + _answers([4, "Y", "Y", "Y", "Y", "Y", "N", "py.test", ""]), + _exp( + "choose versions individually and use old fashioned py.test", + [ALL_PY_ENVS_WO_LAST_AS_STRING, "pytest", "py.test"], + ), _cnf(), ), ( - _answers([4, '', '', '', '', '', '', '', '']), - _exp('choose no version individually and defaults'), + _answers([4, "", "", "", "", "", "", "", ""]), + _exp("choose no version individually and defaults"), _cnf(), ), ( - _answers([4, 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'python -m unittest discover', '']), - _exp('choose versions individually and use nose with default deps', - [ALL_PY_ENVS_WO_LAST_AS_STRING, '', 'python -m unittest discover']), + _answers([4, "Y", "Y", "Y", "Y", "Y", "N", "python -m unittest discover", ""]), + _exp( + "choose versions individually and use nose with default deps", + [ALL_PY_ENVS_WO_LAST_AS_STRING, "", "python -m unittest discover"], + ), _cnf(), ), ( - _answers([4, 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'nosetests', 'nose']), - _exp('choose versions individually and use nose with default deps', - [ALL_PY_ENVS_WO_LAST_AS_STRING, 'nose', 'nosetests']), + _answers([4, "Y", "Y", "Y", "Y", "Y", "N", "nosetests", "nose"]), + _exp( + "choose versions individually and use nose with default deps", + [ALL_PY_ENVS_WO_LAST_AS_STRING, "nose", "nosetests"], + ), _cnf(), ), ( - _answers([4, 'Y', 'Y', 'Y', 'Y', 'Y', 'N', 'trial', '']), - _exp('choose versions individually and use twisted tests with default deps', - [ALL_PY_ENVS_WO_LAST_AS_STRING, 'twisted', 'trial']), + _answers([4, "Y", "Y", "Y", "Y", "Y", "N", "trial", ""]), + _exp( + "choose versions individually and use twisted tests with default deps", + [ALL_PY_ENVS_WO_LAST_AS_STRING, "twisted", "trial"], + ), _cnf(), ), ( - _answers([4, '', '', '', '', '', '', '', '']), - _exp('existing not overriden, generated to alternative with default name'), + _answers([4, "", "", "", "", "", "", "", ""]), + _exp("existing not overriden, generated to alternative with default name"), _cnf(exists=True), ), ( - _answers([4, '', '', '', '', '', '', '', '']), - _exp('existing not overriden, generated to alternative with custom name'), - _cnf(exists=True, names=['some-other.ini']), + _answers([4, "", "", "", "", "", "", "", ""]), + _exp("existing not overriden, generated to alternative with custom name"), + _cnf(exists=True, names=["some-other.ini"]), ), ( - _answers([4, '', '', '', '', '', '', '', '']), - _exp('existing not override, generated to alternative'), - _cnf(exists=True, names=['tox.ini', 'some-other.ini']), + _answers([4, "", "", "", "", "", "", "", ""]), + _exp("existing not override, generated to alternative"), + _cnf(exists=True, names=["tox.ini", "some-other.ini"]), ), ( - _answers([4, '', '', '', '', '', '', '', '']), - _exp('existing alternatives are not overriden, generated to alternative'), - _cnf(exists=True, names=['tox.ini', 'setup.py', 'some-other.ini']), + _answers([4, "", "", "", "", "", "", "", ""]), + _exp("existing alternatives are not overriden, generated to alternative"), + _cnf(exists=True, names=["tox.ini", "setup.py", "some-other.ini"]), ), -)) + ), +) def test_quickstart(answers, cnf, exp, monkeypatch): """Test quickstart script using some little helpers. @@ -215,13 +236,13 @@ def test_quickstart(answers, cnf, exp, monkeypatch): :param _cnf cnf: helper for args and config file paths and contents :param _exp exp: expectation helper """ - monkeypatch.setattr('six.moves.input', answers) - monkeypatch.setattr('sys.argv', cnf.argv) + monkeypatch.setattr("six.moves.input", answers) + monkeypatch.setattr("sys.argv", cnf.argv) if cnf.exists: answers.extend(cnf.names) cnf.create() main() - print("generated config at %s:\n%s\n" % (cnf.path_to_generated, cnf.generated_content)) + print("generated config at {}:\n{}\n".format(cnf.path_to_generated, cnf.generated_content)) check_basic_sanity(cnf.generated_content, SIGNS_OF_SANITY) assert cnf.generated_content == exp.content if cnf.exists: @@ -231,4 +252,4 @@ def test_quickstart(answers, cnf, exp, monkeypatch): def check_basic_sanity(content, signs): for sign in signs: if sign not in content: - pytest.fail("%s not in\n%s" % (sign, content)) + pytest.fail("{} not in\n{}".format(sign, content)) diff --git a/tests/test_result.py b/tests/test_result.py index 924193fde..38f02bc15 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -39,10 +39,12 @@ def test_set_header(pkg): assert replog.dict["toxversion"] == tox.__version__ assert replog.dict["platform"] == sys.platform assert replog.dict["host"] == socket.getfqdn() - assert replog.dict["installpkg"] == { + expected = { "basename": "hello-1.0.tar.gz", "md5": pkg.computehash("md5"), - "sha256": pkg.computehash("sha256")} + "sha256": pkg.computehash("sha256"), + } + assert replog.dict["installpkg"] == expected data = replog.dumps_json() replog2 = ResultLog(data) assert replog2.dict == replog.dict @@ -65,31 +67,30 @@ def test_get_commandlog(pkg): assert "setup" not in envlog.dict setuplog = envlog.get_commandlog("setup") setuplog.add_command(["virtualenv", "..."], "venv created", 0) - assert setuplog.list == [{"command": ["virtualenv", "..."], - "output": "venv created", - "retcode": "0"}] + expected = [{"command": ["virtualenv", "..."], "output": "venv created", "retcode": "0"}] + assert setuplog.list == expected assert envlog.dict["setup"] setuplog2 = replog.get_envlog("py36").get_commandlog("setup") assert setuplog2.list == setuplog.list -@pytest.mark.parametrize('exit_code', [None, 0, 5, 128 + signal.SIGTERM, 1234]) -@pytest.mark.parametrize('os_name', ['posix', 'nt']) +@pytest.mark.parametrize("exit_code", [None, 0, 5, 128 + signal.SIGTERM, 1234]) +@pytest.mark.parametrize("os_name", ["posix", "nt"]) def test_invocation_error(exit_code, os_name, mocker, monkeypatch): - monkeypatch.setattr(os, 'name', value=os_name) - mocker.spy(tox.exception, 'exit_code_str') + monkeypatch.setattr(os, "name", value=os_name) + mocker.spy(tox.exception, "exit_code_str") result = str(tox.exception.InvocationError("", exit_code=exit_code)) # check that mocker works, because it will be our only test in # test_z_cmdline.py::test_exit_code needs the mocker.spy above assert tox.exception.exit_code_str.call_count == 1 - assert tox.exception.exit_code_str.call_args == mocker.call( - 'InvocationError', "", exit_code) + call_args = tox.exception.exit_code_str.call_args + assert call_args == mocker.call("InvocationError", "", exit_code) if exit_code is None: assert "(exited with code" not in result else: assert "(exited with code %d)" % exit_code in result note = "Note: this might indicate a fatal error signal" - if (os_name == 'posix') and (exit_code == 128 + signal.SIGTERM): + if (os_name == "posix") and (exit_code == 128 + signal.SIGTERM): assert note in result assert "({} - 128 = {}: SIGTERM)".format(exit_code, signal.SIGTERM) in result else: diff --git a/tests/test_venv.py b/tests/test_venv.py index 912f03f68..0d7ec60a9 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -7,8 +7,13 @@ import tox from tox.interpreters import NoInterpreterInfo from tox.venv import ( - CreationConfig, VirtualEnv, getdigest, prepend_shebang_interpreter, - tox_testenv_create, tox_testenv_install_deps) + CreationConfig, + VirtualEnv, + getdigest, + prepend_shebang_interpreter, + tox_testenv_create, + tox_testenv_install_deps, +) def test_getdigest(tmpdir): @@ -16,21 +21,26 @@ def test_getdigest(tmpdir): def test_getsupportedinterpreter(monkeypatch, newconfig, mocksession): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:python] - basepython=%s - """ % sys.executable) - venv = VirtualEnv(config.envconfigs['python'], session=mocksession) + basepython={} + """.format( + sys.executable + ), + ) + venv = VirtualEnv(config.envconfigs["python"], session=mocksession) interp = venv.getsupportedinterpreter() # realpath needed for debian symlinks assert py.path.local(interp).realpath() == py.path.local(sys.executable).realpath() - monkeypatch.setattr(tox.INFO, 'IS_WIN', True) - monkeypatch.setattr(venv.envconfig, 'basepython', 'jython') + monkeypatch.setattr(tox.INFO, "IS_WIN", True) + monkeypatch.setattr(venv.envconfig, "basepython", "jython") with pytest.raises(tox.exception.UnsupportedInterpreter): venv.getsupportedinterpreter() monkeypatch.undo() monkeypatch.setattr(venv.envconfig, "envname", "py1") - monkeypatch.setattr(venv.envconfig, 'basepython', 'notexistingpython') + monkeypatch.setattr(venv.envconfig, "basepython", "notexistingpython") with pytest.raises(tox.exception.InterpreterNotFound): venv.getsupportedinterpreter() monkeypatch.undo() @@ -43,10 +53,13 @@ def test_getsupportedinterpreter(monkeypatch, newconfig, mocksession): def test_create(mocksession, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:py123] - """) - envconfig = config.envconfigs['py123'] + """, + ) + envconfig = config.envconfigs["py123"] venv = VirtualEnv(envconfig, session=mocksession) assert venv.path == envconfig.envdir assert not venv.path.check() @@ -58,7 +71,8 @@ def test_create(mocksession, newconfig): assert "virtualenv" == str(args[2]) if not tox.INFO.IS_WIN: # realpath is needed for stuff like the debian symlinks - assert py.path.local(sys.executable).realpath() == py.path.local(args[0]).realpath() + our_sys_path = py.path.local(sys.executable).realpath() + assert our_sys_path == py.path.local(args[0]).realpath() # assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python @@ -67,10 +81,13 @@ def test_create(mocksession, newconfig): def test_commandpath_venv_precedence(tmpdir, monkeypatch, mocksession, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:py123] - """) - envconfig = config.envconfigs['py123'] + """, + ) + envconfig = config.envconfigs["py123"] venv = VirtualEnv(envconfig, session=mocksession) tmpdir.ensure("easy_install") monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep) @@ -80,14 +97,17 @@ def test_commandpath_venv_precedence(tmpdir, monkeypatch, mocksession, newconfig def test_create_sitepackages(mocksession, newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:site] sitepackages=True [testenv:nosite] sitepackages=False - """) - envconfig = config.envconfigs['site'] + """, + ) + envconfig = config.envconfigs["site"] venv = VirtualEnv(envconfig, session=mocksession) action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) @@ -97,7 +117,7 @@ def test_create_sitepackages(mocksession, newconfig): assert "--system-site-packages" in map(str, args) mocksession._clearmocks() - envconfig = config.envconfigs['nosite'] + envconfig = config.envconfigs["nosite"] venv = VirtualEnv(envconfig, session=mocksession) action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) @@ -109,13 +129,16 @@ def test_create_sitepackages(mocksession, newconfig): def test_install_deps_wildcard(newmocksession): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [tox] distshare = {toxworkdir}/distshare [testenv:py123] deps= {distshare}/dep1-* - """) + """, + ) venv = mocksession.getenv("py123") action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) @@ -136,7 +159,9 @@ def test_install_deps_wildcard(newmocksession): def test_install_deps_indexserver(newmocksession): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [tox] indexserver = abc = ABC @@ -146,8 +171,9 @@ def test_install_deps_indexserver(newmocksession): dep1 :abc:dep2 :abc2:dep3 - """) - venv = mocksession.getenv('py123') + """, + ) + venv = mocksession.getenv("py123") action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) pcalls = mocksession._pcalls @@ -170,13 +196,16 @@ def test_install_deps_indexserver(newmocksession): def test_install_deps_pre(newmocksession): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv] pip_pre=true deps= dep1 - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) pcalls = mocksession._pcalls @@ -191,12 +220,15 @@ def test_install_deps_pre(newmocksession): def test_installpkg_indexserver(newmocksession, tmpdir): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [tox] indexserver = default = ABC - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") pcalls = mocksession._pcalls p = tmpdir.ensure("distfile.tar.gz") mocksession.installpkg(venv, p) @@ -208,11 +240,14 @@ def test_installpkg_indexserver(newmocksession, tmpdir): def test_install_recreate(newmocksession, tmpdir): pkg = tmpdir.ensure("package.tar.gz") - mocksession = newmocksession(['--recreate'], """ + mocksession = newmocksession( + ["--recreate"], + """ [testenv] deps=xyz - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") action = mocksession.newaction(venv, "update") venv.update(action) @@ -223,29 +258,35 @@ def test_install_recreate(newmocksession, tmpdir): def test_install_sdist_extras(newmocksession): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv] extras = testing development - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) pcalls = mocksession._pcalls assert len(pcalls) == 1 pcalls[:] = [] - venv.installpkg('distfile.tar.gz', action=action) - assert 'distfile.tar.gz[testing,development]' in pcalls[-1].args + venv.installpkg("distfile.tar.gz", action=action) + assert "distfile.tar.gz[testing,development]" in pcalls[-1].args def test_develop_extras(newmocksession, tmpdir): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv] extras = testing development - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) pcalls = mocksession._pcalls @@ -253,7 +294,7 @@ def test_develop_extras(newmocksession, tmpdir): pcalls[:] = [] venv.developpkg(tmpdir, action=action) - expected = "%s[testing,development]" % tmpdir.strpath + expected = "{}[testing,development]".format(tmpdir.strpath) assert expected in pcalls[-1].args @@ -261,14 +302,17 @@ def test_env_variables_added_to_needs_reinstall(tmpdir, mocksession, newconfig, tmpdir.ensure("setup.py") monkeypatch.setenv("TEMP_PASS_VAR", "123") monkeypatch.setenv("TEMP_NOPASS_VAR", "456") - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:python] passenv = temp_pass_var setenv = CUSTOM_VAR = 789 - """) + """, + ) - venv = VirtualEnv(config.envconfigs['python'], session=mocksession) + venv = VirtualEnv(config.envconfigs["python"], session=mocksession) action = mocksession.newaction(venv, "hello") venv._needs_reinstall(tmpdir, action) @@ -278,24 +322,24 @@ def test_env_variables_added_to_needs_reinstall(tmpdir, mocksession, newconfig, env = pcalls[0].env # should have access to setenv vars - assert 'CUSTOM_VAR' in env - assert env['CUSTOM_VAR'] == '789' + assert "CUSTOM_VAR" in env + assert env["CUSTOM_VAR"] == "789" # should have access to passenv vars - assert 'TEMP_PASS_VAR' in env - assert env['TEMP_PASS_VAR'] == "123" + assert "TEMP_PASS_VAR" in env + assert env["TEMP_PASS_VAR"] == "123" # should also have access to full invocation environment, # for backward compatibility, and to match behavior of venv.run_install_command() - assert 'TEMP_NOPASS_VAR' in env + assert "TEMP_NOPASS_VAR" in env assert env["TEMP_NOPASS_VAR"] == "456" def test_test_hashseed_is_in_output(newmocksession, monkeypatch): - seed = '123456789' - monkeypatch.setattr('tox.config.make_hashseed', lambda: seed) + seed = "123456789" + monkeypatch.setattr("tox.config.make_hashseed", lambda: seed) mocksession = newmocksession([], "") - venv = mocksession.getenv('python') + venv = mocksession.getenv("python") action = mocksession.newaction(venv, "update") venv.update(action) venv.test() @@ -303,11 +347,14 @@ def test_test_hashseed_is_in_output(newmocksession, monkeypatch): def test_test_runtests_action_command_is_in_output(newmocksession): - mocksession = newmocksession([], ''' + mocksession = newmocksession( + [], + """ [testenv] commands = echo foo bar - ''') - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") action = mocksession.newaction(venv, "update") venv.update(action) venv.test() @@ -315,74 +362,88 @@ def test_test_runtests_action_command_is_in_output(newmocksession): def test_install_error(newmocksession): - mocksession = newmocksession(['--recreate'], """ + mocksession = newmocksession( + ["--recreate"], + """ [testenv] deps=xyz commands= qwelkqw - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") venv.test() mocksession.report.expect("error", "*not find*qwelkqw*") assert venv.status == "commands failed" def test_install_command_not_installed(newmocksession): - mocksession = newmocksession(['--recreate'], """ + mocksession = newmocksession( + ["--recreate"], + """ [testenv] commands= pytest - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") venv.test() mocksession.report.expect("warning", "*test command found but not*") assert venv.status == 0 def test_install_command_whitelisted(newmocksession): - mocksession = newmocksession(['--recreate'], """ + mocksession = newmocksession( + ["--recreate"], + """ [testenv] whitelist_externals = pytest xy* commands= pytest xyz - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") venv.test() - mocksession.report.expect("warning", "*test command found but not*", - invert=True) + mocksession.report.expect("warning", "*test command found but not*", invert=True) assert venv.status == "commands failed" def test_install_command_not_installed_bash(newmocksession): - mocksession = newmocksession(['--recreate'], """ + mocksession = newmocksession( + ["--recreate"], + """ [testenv] commands= bash - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") venv.test() mocksession.report.expect("warning", "*test command found but not*") def test_install_python3(newmocksession): - if not py.path.local.sysfind('python3'): + if not py.path.local.sysfind("python3"): pytest.skip("needs python3") - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv:py123] basepython=python3 deps= dep1 dep2 - """) - venv = mocksession.getenv('py123') + """, + ) + venv = mocksession.getenv("py123") action = mocksession.newaction(venv, "getenv") tox_testenv_create(action=action, venv=venv) pcalls = mocksession._pcalls assert len(pcalls) == 1 args = pcalls[0].args - assert str(args[2]) == 'virtualenv' + assert str(args[2]) == "virtualenv" pcalls[:] = [] action = mocksession.newaction(venv, "hello") venv._install(["hello"], action=action) @@ -394,9 +455,10 @@ def test_install_python3(newmocksession): class TestCreationConfig: + def test_basic(self, newconfig, mocksession, tmpdir): config = newconfig([], "") - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() assert cconfig.matches(cconfig) @@ -407,33 +469,42 @@ def test_basic(self, newconfig, mocksession, tmpdir): assert cconfig.matches(newconfig) def test_matchingdependencies(self, newconfig, mocksession): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv] deps=abc - """) - envconfig = config.envconfigs['python'] + """, + ) + envconfig = config.envconfigs["python"] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() - config = newconfig([], """ + config = newconfig( + [], + """ [testenv] deps=xyz - """) - envconfig = config.envconfigs['python'] + """, + ) + envconfig = config.envconfigs["python"] venv = VirtualEnv(envconfig, session=mocksession) otherconfig = venv._getliveconfig() assert not cconfig.matches(otherconfig) def test_matchingdependencies_file(self, newconfig, mocksession): - config = newconfig([], """ + config = newconfig( + [], + """ [tox] distshare={toxworkdir}/distshare [testenv] deps=abc {distshare}/xyz.zip - """) + """, + ) xyz = config.distshare.join("xyz.zip") xyz.ensure() - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() assert cconfig.matches(cconfig) @@ -442,15 +513,18 @@ def test_matchingdependencies_file(self, newconfig, mocksession): assert not cconfig.matches(newconfig) def test_matchingdependencies_latest(self, newconfig, mocksession): - config = newconfig([], """ + config = newconfig( + [], + """ [tox] distshare={toxworkdir}/distshare [testenv] deps={distshare}/xyz-* - """) + """, + ) config.distshare.ensure("xyz-1.2.0.zip") xyz2 = config.distshare.ensure("xyz-1.2.1.zip") - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() md5, path = cconfig.deps[0] @@ -460,7 +534,7 @@ def test_matchingdependencies_latest(self, newconfig, mocksession): def test_python_recreation(self, tmpdir, newconfig, mocksession): pkg = tmpdir.ensure("package.tar.gz") config = newconfig([], "") - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] venv = VirtualEnv(envconfig, session=mocksession) cconfig = venv._getliveconfig() action = mocksession.newaction(venv, "update") @@ -470,7 +544,7 @@ def test_python_recreation(self, tmpdir, newconfig, mocksession): assert venv.path_config.check() assert mocksession._pcalls args1 = map(str, mocksession._pcalls[0].args) - assert 'virtualenv' in " ".join(args1) + assert "virtualenv" in " ".join(args1) mocksession.report.expect("*", "*create*") # modify config and check that recreation happens mocksession._clearmocks() @@ -486,7 +560,7 @@ def test_python_recreation(self, tmpdir, newconfig, mocksession): def test_dep_recreation(self, newconfig, mocksession): config = newconfig([], "") - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] venv = VirtualEnv(envconfig, session=mocksession) action = mocksession.newaction(venv, "update") venv.update(action) @@ -500,7 +574,7 @@ def test_dep_recreation(self, newconfig, mocksession): def test_develop_recreation(self, newconfig, mocksession): config = newconfig([], "") - envconfig = config.envconfigs['python'] + envconfig = config.envconfigs["python"] venv = VirtualEnv(envconfig, session=mocksession) action = mocksession.newaction(venv, "update") venv.update(action) @@ -514,69 +588,81 @@ def test_develop_recreation(self, newconfig, mocksession): class TestVenvTest: + def test_envbindir_path(self, newmocksession, monkeypatch): monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1") - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv:python] commands=abc - """) + """, + ) venv = mocksession.getenv("python") action = mocksession.newaction(venv, "getenv") monkeypatch.setenv("PATH", "xyz") sysfind_calls = [] - monkeypatch.setattr("py.path.local.sysfind", classmethod( - lambda *args, **kwargs: sysfind_calls.append(kwargs) or 0 / 0)) + monkeypatch.setattr( + "py.path.local.sysfind", + classmethod(lambda *args, **kwargs: sysfind_calls.append(kwargs) or 0 / 0), + ) with pytest.raises(ZeroDivisionError): - venv._install(list('123'), action=action) + venv._install(list("123"), action=action) assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir] with pytest.raises(ZeroDivisionError): venv.test(action) assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir] with pytest.raises(ZeroDivisionError): - venv.run_install_command(['qwe'], action=action) + venv.run_install_command(["qwe"], action=action) assert sysfind_calls.pop()["paths"] == [venv.envconfig.envbindir] monkeypatch.setenv("PIP_RESPECT_VIRTUALENV", "1") monkeypatch.setenv("PIP_REQUIRE_VIRTUALENV", "1") monkeypatch.setenv("__PYVENV_LAUNCHER__", "1") with pytest.raises(ZeroDivisionError): - venv.run_install_command(['qwe'], action=action) - assert 'PIP_RESPECT_VIRTUALENV' not in os.environ - assert 'PIP_REQUIRE_VIRTUALENV' not in os.environ - assert '__PYVENV_LAUNCHER__' not in os.environ + venv.run_install_command(["qwe"], action=action) + assert "PIP_RESPECT_VIRTUALENV" not in os.environ + assert "PIP_REQUIRE_VIRTUALENV" not in os.environ + assert "__PYVENV_LAUNCHER__" not in os.environ def test_pythonpath_usage(self, newmocksession, monkeypatch): monkeypatch.setenv("PYTHONPATH", "/my/awesome/library") - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv:python] commands=abc - """) + """, + ) venv = mocksession.getenv("python") action = mocksession.newaction(venv, "getenv") - venv.run_install_command(['qwe'], action=action) - assert 'PYTHONPATH' not in os.environ + venv.run_install_command(["qwe"], action=action) + assert "PYTHONPATH" not in os.environ mocksession.report.expect("warning", "*Discarding $PYTHONPATH from environment*") pcalls = mocksession._pcalls assert len(pcalls) == 1 - assert 'PYTHONPATH' not in pcalls[0].env + assert "PYTHONPATH" not in pcalls[0].env # passenv = PYTHONPATH allows PYTHONPATH to stay in environment monkeypatch.setenv("PYTHONPATH", "/my/awesome/library") - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv:python] commands=abc passenv = PYTHONPATH - """) + """, + ) venv = mocksession.getenv("python") action = mocksession.newaction(venv, "getenv") - venv.run_install_command(['qwe'], action=action) - assert 'PYTHONPATH' in os.environ + venv.run_install_command(["qwe"], action=action) + assert "PYTHONPATH" in os.environ mocksession.report.not_expect("warning", "*Discarding $PYTHONPATH from environment*") pcalls = mocksession._pcalls assert len(pcalls) == 2 - assert pcalls[1].env['PYTHONPATH'] == '/my/awesome/library' + assert pcalls[1].env["PYTHONPATH"] == "/my/awesome/library" # FIXME this test fails when run in isolation - find what this depends on @@ -585,17 +671,20 @@ def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatc pkg = tmpdir.ensure("package.tar.gz") monkeypatch.setenv("X123", "123") monkeypatch.setenv("YY", "456") - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:python] commands=python -V passenv = x123 setenv = ENV_VAR = value PYTHONPATH = value - """) + """, + ) mocksession._clearmocks() - venv = VirtualEnv(config.envconfigs['python'], session=mocksession) + venv = VirtualEnv(config.envconfigs["python"], session=mocksession) mocksession.installpkg(venv, pkg) venv.test() @@ -604,18 +693,17 @@ def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatc for x in pcalls: env = x.env assert env is not None - assert 'ENV_VAR' in env - assert env['ENV_VAR'] == 'value' - assert env['VIRTUAL_ENV'] == str(venv.path) - assert env['X123'] == "123" - assert 'PYTHONPATH' in env - assert env['PYTHONPATH'] == 'value' + assert "ENV_VAR" in env + assert env["ENV_VAR"] == "value" + assert env["VIRTUAL_ENV"] == str(venv.path) + assert env["X123"] == "123" + assert "PYTHONPATH" in env + assert env["PYTHONPATH"] == "value" # all env variables are passed for installation assert pcalls[0].env["YY"] == "456" assert "YY" not in pcalls[1].env - assert {"ENV_VAR", "VIRTUAL_ENV", "PYTHONHASHSEED", "X123", "PATH"} \ - .issubset(pcalls[1].env) + assert {"ENV_VAR", "VIRTUAL_ENV", "PYTHONHASHSEED", "X123", "PATH"}.issubset(pcalls[1].env) # setenv does not trigger PYTHONPATH warnings mocksession.report.not_expect("warning", "*Discarding $PYTHONPATH from environment*") @@ -627,64 +715,70 @@ def test_env_variables_added_to_pcall(tmpdir, mocksession, newconfig, monkeypatc def test_installpkg_no_upgrade(tmpdir, newmocksession): pkg = tmpdir.ensure("package.tar.gz") mocksession = newmocksession([], "") - venv = mocksession.getenv('python') + venv = mocksession.getenv("python") venv.just_created = True venv.envconfig.envdir.ensure(dir=1) mocksession.installpkg(venv, pkg) pcalls = mocksession._pcalls assert len(pcalls) == 1 - assert '-U' not in pcalls[0].args + assert "-U" not in pcalls[0].args def test_installpkg_upgrade(newmocksession, tmpdir): pkg = tmpdir.ensure("package.tar.gz") mocksession = newmocksession([], "") - venv = mocksession.getenv('python') - assert not hasattr(venv, 'just_created') + venv = mocksession.getenv("python") + assert not hasattr(venv, "just_created") mocksession.installpkg(venv, pkg) pcalls = mocksession._pcalls assert len(pcalls) == 1 index = pcalls[0].args.index(str(pkg)) assert index >= 0 - assert '-U' in pcalls[0].args[:index] - assert '--no-deps' in pcalls[0].args[:index] + assert "-U" in pcalls[0].args[:index] + assert "--no-deps" in pcalls[0].args[:index] def test_run_install_command(newmocksession): mocksession = newmocksession([], "") - venv = mocksession.getenv('python') + venv = mocksession.getenv("python") venv.just_created = True venv.envconfig.envdir.ensure(dir=1) action = mocksession.newaction(venv, "hello") venv.run_install_command(packages=["whatever"], action=action) pcalls = mocksession._pcalls assert len(pcalls) == 1 - assert 'pip' in pcalls[0].args[0] - assert 'install' in pcalls[0].args + assert "pip" in pcalls[0].args[0] + assert "install" in pcalls[0].args env = pcalls[0].env assert env is not None def test_run_custom_install_command(newmocksession): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv] install_command=easy_install {opts} {packages} - """) - venv = mocksession.getenv('python') + """, + ) + venv = mocksession.getenv("python") venv.just_created = True venv.envconfig.envdir.ensure(dir=1) action = mocksession.newaction(venv, "hello") venv.run_install_command(packages=["whatever"], action=action) pcalls = mocksession._pcalls assert len(pcalls) == 1 - assert 'easy_install' in pcalls[0].args[0] - assert pcalls[0].args[1:] == ['whatever'] + assert "easy_install" in pcalls[0].args[0] + assert pcalls[0].args[1:] == ["whatever"] def test_command_relative_issue36(newmocksession, tmpdir, monkeypatch): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv] - """) + """, + ) x = tmpdir.ensure("x") venv = mocksession.getenv("python") x2 = venv.getcommandpath("./x", cwd=tmpdir) @@ -695,28 +789,31 @@ def test_command_relative_issue36(newmocksession, tmpdir, monkeypatch): mocksession.report.not_expect("warning", "*test command found but not*") monkeypatch.setenv("PATH", str(tmpdir)) x4 = venv.getcommandpath("x", cwd=tmpdir) - assert x4.endswith(os.sep + 'x') + assert x4.endswith(os.sep + "x") mocksession.report.expect("warning", "*test command found but not*") def test_ignore_outcome_failing_cmd(newmocksession): - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv] commands=testenv_fail ignore_outcome=True - """) + """, + ) - venv = mocksession.getenv('python') + venv = mocksession.getenv("python") venv.test() assert venv.status == "ignored failed command" - mocksession.report.expect("warning", "*command failed but result from " - "testenv is ignored*") + mocksession.report.expect("warning", "*command failed but result from testenv is ignored*") def test_tox_testenv_create(newmocksession): log = [] class Plugin: + @tox.hookimpl def tox_testenv_create(self, action, venv): assert isinstance(action, tox.session.Action) @@ -729,13 +826,17 @@ def tox_testenv_install_deps(self, action, venv): assert isinstance(venv, VirtualEnv) log.append(2) - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv] commands=testenv_fail ignore_outcome=True - """, plugins=[Plugin()]) + """, + plugins=[Plugin()], + ) - venv = mocksession.getenv('python') + venv = mocksession.getenv("python") venv.update(action=mocksession.newaction(venv, "getenv")) assert log == [1, 2] @@ -744,126 +845,135 @@ def test_tox_testenv_pre_post(newmocksession): log = [] class Plugin: + @tox.hookimpl def tox_runtest_pre(self): - log.append('started') + log.append("started") @tox.hookimpl def tox_runtest_post(self): - log.append('finished') + log.append("finished") - mocksession = newmocksession([], """ + mocksession = newmocksession( + [], + """ [testenv] commands=testenv_fail - """, plugins=[Plugin()]) + """, + plugins=[Plugin()], + ) - venv = mocksession.getenv('python') + venv = mocksession.getenv("python") venv.status = None assert log == [] mocksession.runtestenv(venv) - assert log == ['started', 'finished'] + assert log == ["started", "finished"] @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_empty_instance(tmpdir): - testfile = tmpdir.join('check_shebang_empty_instance.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_empty_instance.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # empty instance - testfile.write('') + testfile.write("") args = prepend_shebang_interpreter(base_args) assert args == base_args @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_empty_interpreter(tmpdir): - testfile = tmpdir.join('check_shebang_empty_interpreter.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_empty_interpreter.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # empty interpreter - testfile.write('#!') + testfile.write("#!") args = prepend_shebang_interpreter(base_args) assert args == base_args @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_empty_interpreter_ws(tmpdir): - testfile = tmpdir.join('check_shebang_empty_interpreter_ws.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_empty_interpreter_ws.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # empty interpreter (whitespaces) - testfile.write('#! \n') + testfile.write("#! \n") args = prepend_shebang_interpreter(base_args) assert args == base_args @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_interpreter_simple(tmpdir): - testfile = tmpdir.join('check_shebang_interpreter_simple.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_interpreter_simple.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # interpreter (simple) - testfile.write('#!interpreter') + testfile.write("#!interpreter") args = prepend_shebang_interpreter(base_args) - assert args == [b'interpreter'] + base_args + assert args == [b"interpreter"] + base_args @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_interpreter_ws(tmpdir): - testfile = tmpdir.join('check_shebang_interpreter_ws.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_interpreter_ws.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # interpreter (whitespaces) - testfile.write('#! interpreter \n\n') + testfile.write("#! interpreter \n\n") args = prepend_shebang_interpreter(base_args) - assert args == [b'interpreter'] + base_args + assert args == [b"interpreter"] + base_args @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_interpreter_arg(tmpdir): - testfile = tmpdir.join('check_shebang_interpreter_arg.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_interpreter_arg.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # interpreter with argument - testfile.write('#!interpreter argx\n') + testfile.write("#!interpreter argx\n") args = prepend_shebang_interpreter(base_args) - assert args == [b'interpreter', b'argx'] + base_args + assert args == [b"interpreter", b"argx"] + base_args @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_interpreter_args(tmpdir): - testfile = tmpdir.join('check_shebang_interpreter_args.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_interpreter_args.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # interpreter with argument (ensure single argument) - testfile.write('#!interpreter argx argx-part2\n') + testfile.write("#!interpreter argx argx-part2\n") args = prepend_shebang_interpreter(base_args) - assert args == [b'interpreter', b'argx argx-part2'] + base_args + assert args == [b"interpreter", b"argx argx-part2"] + base_args @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_real(tmpdir): - testfile = tmpdir.join('check_shebang_real.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_real.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # interpreter (real example) - testfile.write('#!/usr/bin/env python\n') + testfile.write("#!/usr/bin/env python\n") args = prepend_shebang_interpreter(base_args) - assert args == [b'/usr/bin/env', b'python'] + base_args + assert args == [b"/usr/bin/env", b"python"] + base_args @pytest.mark.skipif("sys.platform == 'win32'") def test_tox_testenv_interpret_shebang_long_example(tmpdir): - testfile = tmpdir.join('check_shebang_long_example.py') - base_args = [str(testfile), 'arg1', 'arg2', 'arg3'] + testfile = tmpdir.join("check_shebang_long_example.py") + base_args = [str(testfile), "arg1", "arg2", "arg3"] # interpreter (long example) testfile.write( - '#!this-is-an-example-of-a-very-long-interpret-directive-what-should-' - 'be-directly-invoked-when-tox-needs-to-invoked-the-provided-script-' - 'name-in-the-argument-list') + "#!this-is-an-example-of-a-very-long-interpret-directive-what-should-" + "be-directly-invoked-when-tox-needs-to-invoked-the-provided-script-" + "name-in-the-argument-list" + ) args = prepend_shebang_interpreter(base_args) - assert args == [ - b'this-is-an-example-of-a-very-long-interpret-directive-what-should-be-' - b'directly-invoked-when-tox-needs-to-invoked-the-provided-script-name-' - b'in-the-argument-list'] + base_args + expected = [ + b"this-is-an-example-of-a-very-long-interpret-directive-what-should-be-" + b"directly-invoked-when-tox-needs-to-invoked-the-provided-script-name-" + b"in-the-argument-list" + ] + + assert args == expected + base_args diff --git a/tests/test_z_cmdline.py b/tests/test_z_cmdline.py index 18bcc98b8..3419339ae 100644 --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -18,12 +18,16 @@ def test_report_protocol(newconfig): - config = newconfig([], """ + config = newconfig( + [], + """ [testenv:mypython] deps=xy - """) + """, + ) class Popen: + def __init__(self, *args, **kwargs): pass @@ -78,12 +82,16 @@ def test__resolve_pkg_doubledash(tmpdir, mocksession): class TestSession: + def test_make_sdist(self, initproj): - initproj("example123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' - ''' - }) + initproj( + "example123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ + """, + }, + ) config = parseconfig([]) session = Session(config) sdist = session.get_installpkg_path() @@ -100,13 +108,18 @@ def test_make_sdist(self, initproj): def test_make_sdist_distshare(self, tmpdir, initproj): distshare = tmpdir.join("distshare") - initproj("example123-0.6", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "example123-0.6", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [tox] - distshare=%s - ''' % distshare - }) + distshare={} + """.format( + distshare + ), + }, + ) config = parseconfig([]) session = Session(config) sdist = session.get_installpkg_path() @@ -121,19 +134,22 @@ def test_log_pcall(self, mocksession): mocksession.config.logdir.ensure(dir=1) assert not mocksession.config.logdir.listdir() action = mocksession.newaction(None, "something") - action.popen(["echo", ]) + action.popen(["echo"]) match = mocksession.report.getnext("logpopen") assert match[1].outpath.relto(mocksession.config.logdir) assert match[1].shell is False def test_summary_status(self, initproj, capfd): - initproj("logexample123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "logexample123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [testenv:hello] [testenv:world] - ''' - }) + """, + }, + ) config = parseconfig([]) session = Session(config) envs = session.venvlist @@ -145,19 +161,22 @@ def test_summary_status(self, initproj, capfd): assert not env2.status session._summary() out, err = capfd.readouterr() - exp = "%s: FAIL XYZ" % env1.envconfig.envname + exp = "{}: FAIL XYZ".format(env1.envconfig.envname) assert exp in out - exp = "%s: commands succeeded" % env2.envconfig.envname + exp = "{}: commands succeeded".format(env2.envconfig.envname) assert exp in out def test_getvenv(self, initproj): - initproj("logexample123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "logexample123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [testenv:hello] [testenv:world] - ''' - }) + """, + }, + ) config = parseconfig([]) session = Session(config) venv1 = session.getvenv("hello") @@ -171,78 +190,90 @@ def test_getvenv(self, initproj): def test_minversion(cmd, initproj): - initproj("interp123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "interp123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [tox] minversion = 6.0 - ''' - }) + """, + }, + ) result = cmd("-v") - assert re.match(r'ERROR: MinVersionError: tox version is .*,' - r' required is at least 6.0', result.out) + assert re.match( + r"ERROR: MinVersionError: tox version is .*," r" required is at least 6.0", result.out + ) assert result.ret def test_notoxini_help_still_works(initproj, cmd): - initproj("example123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - }) + initproj("example123-0.5", filedefs={"tests": {"test_hello.py": "def test_hello(): pass"}}) result = cmd("-h") assert result.err == "ERROR: toxini file 'tox.ini' not found\n" - assert result.out.startswith('usage: ') - assert any('--help' in l for l in result.outlines) + assert result.out.startswith("usage: ") + assert any("--help" in l for l in result.outlines), result.outlines assert not result.ret def test_notoxini_help_ini_still_works(initproj, cmd): - initproj("example123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - }) + initproj("example123-0.5", filedefs={"tests": {"test_hello.py": "def test_hello(): pass"}}) result = cmd("--help-ini") - assert any('setenv' in l for l in result.outlines) + assert any("setenv" in l for l in result.outlines), result.outlines assert not result.ret def test_envdir_equals_toxini_errors_out(cmd, initproj): - initproj("interp123-0.7", filedefs={ - 'tox.ini': ''' + initproj( + "interp123-0.7", + filedefs={ + "tox.ini": """ [testenv] envdir={toxinidir} - ''' - }) + """ + }, + ) result = cmd() assert result.outlines[1] == "ERROR: ConfigError: envdir must not equal toxinidir" - assert re.match(r'ERROR: venv \'python\' in .* would delete project', result.outlines[0]) + assert re.match(r"ERROR: venv \'python\' in .* would delete project", result.outlines[0]) assert result.ret def test_run_custom_install_command_error(cmd, initproj): - initproj("interp123-0.5", filedefs={ - 'tox.ini': ''' + initproj( + "interp123-0.5", + filedefs={ + "tox.ini": """ [testenv] install_command=./tox.ini {opts} {packages} - ''' - }) + """ + }, + ) result = cmd() - assert re.match(r"ERROR: invocation failed \(errno \d+\), args: \['.*[/\\]tox\.ini", - result.outlines[-1]) + assert re.match( + r"ERROR: invocation failed \(errno \d+\), args: \['.*[/\\]tox\.ini", result.outlines[-1] + ) assert result.ret def test_unknown_interpreter_and_env(cmd, initproj): - initproj("interp123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "interp123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [testenv:python] basepython=xyz_unknown_interpreter [testenv] changedir=tests - ''' - }) + """, + }, + ) result = cmd() assert result.ret - assert any('ERROR: InterpreterNotFound: xyz_unknown_interpreter' == l for l in result.outlines) + assert any( + "ERROR: InterpreterNotFound: xyz_unknown_interpreter" == l for l in result.outlines + ), result.outlines result = cmd("-exyz") assert result.ret @@ -250,65 +281,81 @@ def test_unknown_interpreter_and_env(cmd, initproj): def test_unknown_interpreter(cmd, initproj): - initproj("interp123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "interp123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [testenv:python] basepython=xyz_unknown_interpreter [testenv] changedir=tests - ''' - }) + """, + }, + ) result = cmd() assert result.ret - assert any('ERROR: InterpreterNotFound: xyz_unknown_interpreter' == l for l in result.outlines) + assert any( + "ERROR: InterpreterNotFound: xyz_unknown_interpreter" == l for l in result.outlines + ), result.outlines def test_skip_platform_mismatch(cmd, initproj): - initproj("interp123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "interp123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [testenv] changedir=tests platform=x123 - ''' - }) + """, + }, + ) result = cmd() assert not result.ret - assert any('SKIPPED: python: platform mismatch' == l for l in result.outlines) + assert any( + "SKIPPED: python: platform mismatch" == l for l in result.outlines + ), result.outlines def test_skip_unknown_interpreter(cmd, initproj): - initproj("interp123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "interp123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [testenv:python] basepython=xyz_unknown_interpreter [testenv] changedir=tests - ''' - }) + """, + }, + ) result = cmd("--skip-missing-interpreters") assert not result.ret - msg = 'SKIPPED: python: InterpreterNotFound: xyz_unknown_interpreter' - assert any(msg == l for l in result.outlines) + msg = "SKIPPED: python: InterpreterNotFound: xyz_unknown_interpreter" + assert any(msg == l for l in result.outlines), result.outlines def test_skip_unknown_interpreter_result_json(cmd, initproj, tmpdir): report_path = tmpdir.join("toxresult.json") - initproj("interp123-0.5", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "interp123-0.5", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [testenv:python] basepython=xyz_unknown_interpreter [testenv] changedir=tests - ''' - }) + """, + }, + ) result = cmd("--skip-missing-interpreters", "--result-json", report_path) assert not result.ret - msg = 'SKIPPED: python: InterpreterNotFound: xyz_unknown_interpreter' - assert any(msg == l for l in result.outlines) + msg = "SKIPPED: python: InterpreterNotFound: xyz_unknown_interpreter" + assert any(msg == l for l in result.outlines), result.outlines setup_result_from_json = json.load(report_path)["testenvs"]["python"]["setup"] for setup_step in setup_result_from_json: assert "InterpreterNotFound" in setup_step["output"] @@ -316,132 +363,158 @@ def test_skip_unknown_interpreter_result_json(cmd, initproj, tmpdir): def test_unknown_dep(cmd, initproj): - initproj("dep123-0.7", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "dep123-0.7", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [testenv] deps=qweqwe123 changedir=tests - ''' - }) + """, + }, + ) result = cmd() assert result.ret - assert result.outlines[-1].startswith('ERROR: python: could not install deps [qweqwe123];') + assert result.outlines[-1].startswith("ERROR: python: could not install deps [qweqwe123];") def test_venv_special_chars_issue252(cmd, initproj): - initproj("pkg123-0.7", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'tox.ini': ''' + initproj( + "pkg123-0.7", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "tox.ini": """ [tox] envlist = special&&1 [testenv:special&&1] changedir=tests - ''' - }) + """, + }, + ) result = cmd() assert result.ret == 0 - pattern = re.compile('special&&1 installed: .*pkg123==0.7.*') - assert any(pattern.match(line) for line in result.outlines) + pattern = re.compile("special&&1 installed: .*pkg123==0.7.*") + assert any(pattern.match(line) for line in result.outlines), result.outlines def test_unknown_environment(cmd, initproj): - initproj("env123-0.7", filedefs={ - 'tox.ini': '' - }) + initproj("env123-0.7", filedefs={"tox.ini": ""}) result = cmd("-e", "qpwoei") assert result.ret assert result.out == "ERROR: unknown environment 'qpwoei'\n" def test_skip_sdist(cmd, initproj): - initproj("pkg123-0.7", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'setup.py': """ + initproj( + "pkg123-0.7", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "setup.py": """ syntax error """, - 'tox.ini': ''' + "tox.ini": """ [tox] skipsdist=True [testenv] commands=python -c "print('done')" - ''' - }) + """, + }, + ) result = cmd() assert result.ret == 0 def test_minimal_setup_py_empty(cmd, initproj): - initproj("pkg123-0.7", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'setup.py': """ + initproj( + "pkg123-0.7", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "setup.py": """ """, - 'tox.ini': '' - }) + "tox.ini": "", + }, + ) result = cmd() assert result.ret == 1 - assert result.outlines[-1] == 'ERROR: setup.py is empty' + assert result.outlines[-1] == "ERROR: setup.py is empty" def test_minimal_setup_py_comment_only(cmd, initproj): - initproj("pkg123-0.7", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'setup.py': """\n# some comment + initproj( + "pkg123-0.7", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "setup.py": """\n# some comment """, - 'tox.ini': '' - - }) + "tox.ini": "", + }, + ) result = cmd() assert result.ret == 1 - assert result.outlines[-1] == 'ERROR: setup.py is empty' + assert result.outlines[-1] == "ERROR: setup.py is empty" def test_minimal_setup_py_non_functional(cmd, initproj): - initproj("pkg123-0.7", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'setup.py': """ + initproj( + "pkg123-0.7", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "setup.py": """ import sys """, - 'tox.ini': '' - - }) + "tox.ini": "", + }, + ) result = cmd() assert result.ret == 1 - assert any(re.match(r'.*ERROR.*check setup.py.*', l) for l in result.outlines) + assert any(re.match(r".*ERROR.*check setup.py.*", l) for l in result.outlines), result.outlines def test_sdist_fails(cmd, initproj): - initproj("pkg123-0.7", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'setup.py': """ + initproj( + "pkg123-0.7", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "setup.py": """ syntax error """, - 'tox.ini': '', - }) + "tox.ini": "", + }, + ) result = cmd() assert result.ret - assert any(re.match(r'.*FAIL.*could not package project.*', l) for l in result.outlines) + assert any( + re.match(r".*FAIL.*could not package project.*", l) for l in result.outlines + ), result.outlines def test_no_setup_py_exits(cmd, initproj): - initproj("pkg123-0.7", filedefs={ - 'tox.ini': """ + initproj( + "pkg123-0.7", + filedefs={ + "tox.ini": """ [testenv] commands=python -c "2 + 2" """ - }) + }, + ) os.remove("setup.py") result = cmd() assert result.ret - assert any(re.match(r'.*ERROR.*No setup.py file found.*', l) for l in result.outlines) + assert any( + re.match(r".*ERROR.*No setup.py file found.*", l) for l in result.outlines + ), result.outlines def test_package_install_fails(cmd, initproj): - initproj("pkg123-0.7", filedefs={ - 'tests': {'test_hello.py': "def test_hello(): pass"}, - 'setup.py': """ + initproj( + "pkg123-0.7", + filedefs={ + "tests": {"test_hello.py": "def test_hello(): pass"}, + "setup.py": """ from setuptools import setup setup( name='pkg123', @@ -453,42 +526,49 @@ def test_package_install_fails(cmd, initproj): install_requires=['qweqwe123'], ) """, - 'tox.ini': '', - }) + "tox.ini": "", + }, + ) result = cmd() assert result.ret - assert result.outlines[-1].startswith('ERROR: python: InvocationError for command ') + assert result.outlines[-1].startswith("ERROR: python: InvocationError for command ") @pytest.fixture def example123(initproj): - yield initproj("example123-0.5", filedefs={ - 'tests': { - 'test_hello.py': """ + yield initproj( + "example123-0.5", + filedefs={ + "tests": { + "test_hello.py": """ def test_hello(pytestconfig): pass - """, - }, - 'tox.ini': ''' + """ + }, + "tox.ini": """ [testenv] changedir=tests commands= pytest --basetemp={envtmpdir} \ --junitxml=junit-{envname}.xml deps=pytest - ''' - }) + """, + }, + ) def test_toxuone_env(cmd, example123): result = cmd() assert not result.ret - assert re.match(r'.*generated\W+xml\W+file.*junit-python\.xml' - r'.*\W+1\W+passed.*', result.out, re.DOTALL) - result = cmd("-epython", ) + assert re.match( + r".*generated\W+xml\W+file.*junit-python\.xml" r".*\W+1\W+passed.*", result.out, re.DOTALL + ) + result = cmd("-epython") assert not result.ret - assert re.match(r'.*\W+1\W+passed.*' - r'summary.*' - r'python:\W+commands\W+succeeded.*', result.out, re.DOTALL) + assert re.match( + r".*\W+1\W+passed.*" r"summary.*" r"python:\W+commands\W+succeeded.*", + result.out, + re.DOTALL, + ) def test_different_config_cwd(cmd, example123, monkeypatch): @@ -496,9 +576,11 @@ def test_different_config_cwd(cmd, example123, monkeypatch): monkeypatch.chdir(example123.dirname) result = cmd("-c", "example123/tox.ini") assert not result.ret - assert re.match(r'.*\W+1\W+passed.*' - r'summary.*' - r'python:\W+commands\W+succeeded.*', result.out, re.DOTALL) + assert re.match( + r".*\W+1\W+passed.*" r"summary.*" r"python:\W+commands\W+succeeded.*", + result.out, + re.DOTALL, + ) def test_json(cmd, example123): @@ -511,36 +593,51 @@ def test_json(cmd, example123): assert result.ret == 1 data = json.load(jsonpath.open("r")) verify_json_report_format(data) - assert re.match(r'.*\W+1\W+failed.*' - r'summary.*' - r'python:\W+commands\W+failed.*', result.out, re.DOTALL) + assert re.match( + r".*\W+1\W+failed.*" r"summary.*" r"python:\W+commands\W+failed.*", result.out, re.DOTALL + ) def test_developz(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ - """}) + initproj( + "example123", + filedefs={ + "tox.ini": """ + """ + }, + ) result = cmd("-vv", "--develop") assert not result.ret assert "sdist-make" not in result.out def test_usedevelop(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ + initproj( + "example123", + filedefs={ + "tox.ini": """ [testenv] usedevelop=True - """}) + """ + }, + ) result = cmd("-vv") assert not result.ret assert "sdist-make" not in result.out def test_usedevelop_mixed(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ + initproj( + "example123", + filedefs={ + "tox.ini": """ [testenv:devenv] usedevelop=True [testenv:nondev] usedevelop=False - """}) + """ + }, + ) # running only 'devenv' should not do sdist result = cmd("-vv", "-e", "devenv") @@ -555,42 +652,51 @@ def test_usedevelop_mixed(initproj, cmd): @pytest.mark.parametrize("src_root", [".", "src"]) def test_test_usedevelop(cmd, initproj, src_root, monkeypatch): - base = initproj("example123-0.5", src_root=src_root, filedefs={ - 'tests': { - 'test_hello.py': """ + base = initproj( + "example123-0.5", + src_root=src_root, + filedefs={ + "tests": { + "test_hello.py": """ def test_hello(pytestconfig): pass - """, - }, - 'tox.ini': ''' + """ + }, + "tox.ini": """ [testenv] usedevelop=True changedir=tests commands= pytest --basetemp={envtmpdir} --junitxml=junit-{envname}.xml [] deps=pytest - ''' - }) + """, + }, + ) result = cmd("-v") assert not result.ret - assert re.match(r'.*generated\W+xml\W+file.*junit-python\.xml' - r'.*\W+1\W+passed.*', result.out, re.DOTALL) + assert re.match( + r".*generated\W+xml\W+file.*junit-python\.xml" r".*\W+1\W+passed.*", result.out, re.DOTALL + ) assert "sdist-make" not in result.out - result = cmd("-epython", ) + result = cmd("-epython") assert not result.ret assert "develop-inst-noop" in result.out - assert re.match(r'.*\W+1\W+passed.*' - r'summary.*' - r'python:\W+commands\W+succeeded.*', result.out, re.DOTALL) + assert re.match( + r".*\W+1\W+passed.*" r"summary.*" r"python:\W+commands\W+succeeded.*", + result.out, + re.DOTALL, + ) # see that things work with a different CWD monkeypatch.chdir(base.dirname) result = cmd("-c", "example123/tox.ini") assert not result.ret assert "develop-inst-noop" in result.out - assert re.match(r'.*\W+1\W+passed.*' - r'summary.*' - r'python:\W+commands\W+succeeded.*', result.out, re.DOTALL) + assert re.match( + r".*\W+1\W+passed.*" r"summary.*" r"python:\W+commands\W+succeeded.*", + result.out, + re.DOTALL, + ) monkeypatch.chdir(base) # see that tests can also fail and retcode is correct @@ -600,13 +706,13 @@ def test_hello(pytestconfig): result = cmd() assert result.ret assert "develop-inst-noop" in result.out - assert re.match(r'.*\W+1\W+failed.*' - r'summary.*' - r'python:\W+commands\W+failed.*', result.out, re.DOTALL) + assert re.match( + r".*\W+1\W+failed.*" r"summary.*" r"python:\W+commands\W+failed.*", result.out, re.DOTALL + ) # test develop is called if setup.py changes setup_py = py.path.local("setup.py") - setup_py.write(setup_py.read() + ' ') + setup_py.write(setup_py.read() + " ") result = cmd() assert result.ret assert "develop-inst-nodeps" in result.out @@ -615,61 +721,83 @@ def test_hello(pytestconfig): def _alwayscopy_not_supported(): # This is due to virtualenv bugs with alwayscopy in some platforms # see: https://github.com/pypa/virtualenv/issues/565 - if hasattr(platform, 'linux_distribution'): + if hasattr(platform, "linux_distribution"): _dist = platform.linux_distribution(full_distribution_name=False) (name, version, arch) = _dist - if any((name == 'centos' and version[0] == '7', - name == 'SuSE' and arch == 'x86_64')): + if any((name == "centos" and version[0] == "7", name == "SuSE" and arch == "x86_64")): return True return False @pytest.mark.skipif(_alwayscopy_not_supported(), reason="Platform doesnt support alwayscopy") def test_alwayscopy(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ + initproj( + "example123", + filedefs={ + "tox.ini": """ [testenv] commands={envpython} --version alwayscopy=True - """}) + """ + }, + ) result = cmd("-vv") assert not result.ret assert "virtualenv --always-copy" in result.out def test_alwayscopy_default(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ + initproj( + "example123", + filedefs={ + "tox.ini": """ [testenv] commands={envpython} --version - """}) + """ + }, + ) result = cmd("-vv") assert not result.ret assert "virtualenv --always-copy" not in result.out def test_empty_activity_ignored(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ + initproj( + "example123", + filedefs={ + "tox.ini": """ [testenv] list_dependencies_command=echo commands={envpython} --version - """}) + """ + }, + ) result = cmd() assert not result.ret assert "installed:" not in result.out def test_empty_activity_shown_verbose(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ + initproj( + "example123", + filedefs={ + "tox.ini": """ [testenv] list_dependencies_command=echo commands={envpython} --version - """}) + """ + }, + ) result = cmd("-v") assert not result.ret assert "installed:" in result.out def test_test_piphelp(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ + initproj( + "example123", + filedefs={ + "tox.ini": """ # content of: tox.ini [testenv] commands=pip -h @@ -677,76 +805,97 @@ def test_test_piphelp(initproj, cmd): basepython=python [testenv:py36] basepython=python - """}) + """ + }, + ) result = cmd() assert not result.ret def test_notest(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ + initproj( + "example123", + filedefs={ + "tox.ini": """ # content of: tox.ini [testenv:py26] basepython=python - """}) + """ + }, + ) result = cmd("-v", "--notest") assert not result.ret - assert re.match(r'.*summary.*' - r'py26\W+skipped\W+tests.*', result.out, re.DOTALL) + assert re.match(r".*summary.*" r"py26\W+skipped\W+tests.*", result.out, re.DOTALL) result = cmd("-v", "--notest", "-epy26") assert not result.ret - assert re.match(r'.*py26\W+reusing.*', result.out, re.DOTALL) + assert re.match(r".*py26\W+reusing.*", result.out, re.DOTALL) def test_PYC(initproj, cmd, monkeypatch): - initproj("example123", filedefs={'tox.ini': ''}) + initproj("example123", filedefs={"tox.ini": ""}) monkeypatch.setenv("PYTHONDOWNWRITEBYTECODE", 1) result = cmd("-v", "--notest") assert not result.ret - assert 'create' in result.out + assert "create" in result.out def test_env_VIRTUALENV_PYTHON(initproj, cmd, monkeypatch): - initproj("example123", filedefs={'tox.ini': ''}) - monkeypatch.setenv("VIRTUALENV_PYTHON", '/FOO') + initproj("example123", filedefs={"tox.ini": ""}) + monkeypatch.setenv("VIRTUALENV_PYTHON", "/FOO") result = cmd("-v", "--notest") assert not result.ret, result.outlines - assert 'create' in result.out + assert "create" in result.out def test_sdistonly(initproj, cmd): - initproj("example123", filedefs={'tox.ini': """ - """}) + initproj( + "example123", + filedefs={ + "tox.ini": """ + """ + }, + ) result = cmd("-v", "--sdistonly") assert not result.ret - assert re.match(r'.*sdist-make.*setup.py.*', result.out, re.DOTALL) + assert re.match(r".*sdist-make.*setup.py.*", result.out, re.DOTALL) assert "-mvirtualenv" not in result.out def test_separate_sdist_no_sdistfile(cmd, initproj, tmpdir): distshare = tmpdir.join("distshare") - initproj(("pkg123-foo", "0.7"), filedefs={ - 'tox.ini': """ + initproj( + ("pkg123-foo", "0.7"), + filedefs={ + "tox.ini": """ [tox] distshare={} - """.format(distshare) - }) + """.format( + distshare + ) + }, + ) result = cmd("--sdistonly") assert not result.ret distshare_files = distshare.listdir() assert len(distshare_files) == 1 sdistfile = distshare_files[0] - assert 'pkg123-foo-0.7.zip' in str(sdistfile) + assert "pkg123-foo-0.7.zip" in str(sdistfile) def test_separate_sdist(cmd, initproj, tmpdir): distshare = tmpdir.join("distshare") - initproj("pkg123-0.7", filedefs={ - 'tox.ini': """ + initproj( + "pkg123-0.7", + filedefs={ + "tox.ini": """ [tox] - distshare=%s - sdistsrc={distshare}/pkg123-0.7.zip - """ % distshare - }) + distshare={} + sdistsrc={{distshare}}/pkg123-0.7.zip + """.format( + distshare + ) + }, + ) result = cmd("--sdistonly") assert not result.ret sdistfiles = distshare.listdir() @@ -759,11 +908,16 @@ def test_separate_sdist(cmd, initproj, tmpdir): def test_sdist_latest(tmpdir, newconfig): distshare = tmpdir.join("distshare") - config = newconfig([], """ + config = newconfig( + [], + """ [tox] - distshare=%s - sdistsrc={distshare}/pkg123-* - """ % distshare) + distshare={} + sdistsrc={{distshare}}/pkg123-* + """.format( + distshare + ), + ) p = distshare.ensure("pkg123-1.4.5.zip") distshare.ensure("pkg123-1.4.5a1.zip") session = Session(config) @@ -773,51 +927,63 @@ def test_sdist_latest(tmpdir, newconfig): def test_installpkg(tmpdir, newconfig): p = tmpdir.ensure("pkg123-1.0.zip") - config = newconfig(["--installpkg=%s" % p], "") + config = newconfig(["--installpkg={}".format(p)], "") session = Session(config) sdist_path = session.get_installpkg_path() assert sdist_path == p def test_envsitepackagesdir(cmd, initproj): - initproj("pkg512-0.0.5", filedefs={ - 'tox.ini': """ + initproj( + "pkg512-0.0.5", + filedefs={ + "tox.ini": """ [testenv] commands= python -c "print(r'X:{envsitepackagesdir}')" - """}) + """ + }, + ) result = cmd() assert result.ret == 0 - assert re.match(r'.*\nX:.*tox.*site-packages.*', result.out, re.DOTALL) + assert re.match(r".*\nX:.*tox.*site-packages.*", result.out, re.DOTALL) def test_envsitepackagesdir_skip_missing_issue280(cmd, initproj): - initproj("pkg513-0.0.5", filedefs={ - 'tox.ini': """ + initproj( + "pkg513-0.0.5", + filedefs={ + "tox.ini": """ [testenv] basepython=/usr/bin/qwelkjqwle commands= {envsitepackagesdir} - """}) + """ + }, + ) result = cmd("--skip-missing-interpreters") assert result.ret == 0 - assert re.match(r'.*SKIPPED:.*qwelkj.*', result.out, re.DOTALL) + assert re.match(r".*SKIPPED:.*qwelkj.*", result.out, re.DOTALL) -@pytest.mark.parametrize('verbosity', ['', '-v', '-vv']) +@pytest.mark.parametrize("verbosity", ["", "-v", "-vv"]) def test_verbosity(cmd, initproj, verbosity): - initproj("pkgX-0.0.5", filedefs={ - 'tox.ini': """ + initproj( + "pkgX-0.0.5", + filedefs={ + "tox.ini": """ [testenv] - """}) + """ + }, + ) result = cmd(verbosity) assert result.ret == 0 needle = "Successfully installed pkgX-0.0.5" - if verbosity == '-vv': - assert any(needle in line for line in result.outlines) + if verbosity == "-vv": + assert any(needle in line for line in result.outlines), result.outlines else: - assert all(needle not in line for line in result.outlines) + assert all(needle not in line for line in result.outlines), result.outlines def verify_json_report_format(data, testenvs=True): @@ -840,24 +1006,27 @@ def verify_json_report_format(data, testenvs=True): def test_envtmpdir(initproj, cmd): - initproj("foo", filedefs={ - # This file first checks that envtmpdir is existent and empty. Then it - # creates an empty file in that directory. The tox command is run - # twice below, so this is to test whether the directory is cleared - # before the second run. - 'check_empty_envtmpdir.py': '''if True: + initproj( + "foo", + filedefs={ + # This file first checks that envtmpdir is existent and empty. Then it + # creates an empty file in that directory. The tox command is run + # twice below, so this is to test whether the directory is cleared + # before the second run. + "check_empty_envtmpdir.py": """if True: import os from sys import argv envtmpdir = argv[1] assert os.path.exists(envtmpdir) assert os.listdir(envtmpdir) == [] open(os.path.join(envtmpdir, 'test'), 'w').close() - ''', - 'tox.ini': ''' + """, + "tox.ini": """ [testenv] commands=python check_empty_envtmpdir.py {envtmpdir} - ''' - }) + """, + }, + ) result = cmd() assert not result.ret @@ -867,42 +1036,47 @@ def test_envtmpdir(initproj, cmd): def test_missing_env_fails(initproj, cmd): - initproj("foo", filedefs={'tox.ini': "[testenv:foo]\ncommands={env:VAR}"}) + initproj("foo", filedefs={"tox.ini": "[testenv:foo]\ncommands={env:VAR}"}) result = cmd() assert result.ret == 1 - assert result.out.endswith("foo: unresolvable substitution(s): 'VAR'." - " Environment variables are missing or defined recursively.\n") + assert result.out.endswith( + "foo: unresolvable substitution(s): 'VAR'." + " Environment variables are missing or defined recursively.\n" + ) def test_tox_console_script(): - result = subprocess.check_call(['tox', '--help']) + result = subprocess.check_call(["tox", "--help"]) assert result == 0 def test_tox_quickstart_script(): - result = subprocess.check_call(['tox-quickstart', '--help']) + result = subprocess.check_call(["tox-quickstart", "--help"]) assert result == 0 def test_tox_cmdline_no_args(monkeypatch): - monkeypatch.setattr(sys, 'argv', ['caller_script', '--help']) + monkeypatch.setattr(sys, "argv", ["caller_script", "--help"]) with pytest.raises(SystemExit): tox.cmdline() def test_tox_cmdline_args(): with pytest.raises(SystemExit): - tox.cmdline(['caller_script', '--help']) + tox.cmdline(["caller_script", "--help"]) -@pytest.mark.parametrize('exit_code', [0, 6]) +@pytest.mark.parametrize("exit_code", [0, 6]) def test_exit_code(initproj, cmd, exit_code, mocker): """ Check for correct InvocationError, with exit code, except for zero exit code """ import tox.exception - mocker.spy(tox.exception, 'exit_code_str') - tox_ini_content = "[testenv:foo]\ncommands=python -c 'import sys; sys.exit(%d)'" % exit_code - initproj("foo", filedefs={'tox.ini': tox_ini_content}) + + mocker.spy(tox.exception, "exit_code_str") + tox_ini_content = "[testenv:foo]\ncommands=python -c 'import sys; sys.exit({:d})'".format( + exit_code + ) + initproj("foo", filedefs={"tox.ini": tox_ini_content}) cmd() if exit_code: # need mocker.spy above @@ -910,10 +1084,10 @@ def test_exit_code(initproj, cmd, exit_code, mocker): (args, kwargs) = tox.exception.exit_code_str.call_args assert kwargs == {} (call_error_name, call_command, call_exit_code) = args - assert call_error_name == 'InvocationError' + assert call_error_name == "InvocationError" # quotes are removed in result.out # do not include "python" as it is changed to python.EXE by appveyor - expected_command_arg = ' -c import sys; sys.exit(%d)' % exit_code + expected_command_arg = " -c import sys; sys.exit({:d})".format(exit_code) assert expected_command_arg in call_command assert call_exit_code == exit_code else: diff --git a/tox.ini b/tox.ini index da2b21069..786fb2514 100644 --- a/tox.ini +++ b/tox.ini @@ -35,9 +35,9 @@ passenv = {[testenv]passenv} PROGRAMDATA extras = lint description = run static analysis and style check using flake8 -commands = python -m flake8 --show-source tox setup.py {posargs} +commands = pre-commit run --all-files --show-diff-on-failure + python -m flake8 --show-source tox setup.py {posargs} python -m flake8 --show-source doc tests {posargs} - pre-commit run --all-files --show-diff-on-failure python -c 'print("hint: run {envdir}/bin/pre-commit install to add checks as pre-commit hook")' diff --git a/tox/__init__.py b/tox/__init__.py index c7560c825..81d7f6885 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -6,35 +6,33 @@ To override/modify tox behaviour via plugins see `tox.hookspec` and its use with pluggy. """ -from pkg_resources import DistributionNotFound, get_distribution - import pluggy +from pkg_resources import DistributionNotFound, get_distribution from . import exception from .constants import INFO, PIP, PYTHON from .hookspecs import hookspec __all__ = ( - '__version__', # tox version - 'cmdline', # run tox as part of another program/IDE (same behaviour as called standalone) - 'hookimpl', # Hook implementation marker to be imported by plugins - 'exception', # tox specific exceptions - + "__version__", # tox version + "cmdline", # run tox as part of another program/IDE (same behaviour as called standalone) + "hookimpl", # Hook implementation marker to be imported by plugins + "exception", # tox specific exceptions # EXPERIMENTAL CONSTANTS API - 'PYTHON', 'INFO', 'PIP', - + "PYTHON", + "INFO", + "PIP", # DEPRECATED - will be removed from API in tox 4 - 'hookspec', + "hookspec", ) hookimpl = pluggy.HookimplMarker("tox") try: _full_version = get_distribution(__name__).version - __version__ = _full_version.split('+', 1)[0] + __version__ = _full_version.split("+", 1)[0] except DistributionNotFound: - __version__ = '0.0.0.dev0' - + __version__ = "0.0.0.dev0" # NOTE: must come last due to circular import from .session import cmdline # noqa diff --git a/tox/__main__.py b/tox/__main__.py index 7d3c3cc15..821fa4800 100644 --- a/tox/__main__.py +++ b/tox/__main__.py @@ -1,4 +1,4 @@ import tox -if __name__ == '__main__': +if __name__ == "__main__": tox.cmdline() diff --git a/tox/_pytestplugin.py b/tox/_pytestplugin.py index 71d1f45ac..5f5133bcd 100644 --- a/tox/_pytestplugin.py +++ b/tox/_pytestplugin.py @@ -15,24 +15,28 @@ from tox.session import Session, main from tox.venv import VirtualEnv -mark_dont_run_on_windows = pytest.mark.skipif(os.name == 'nt', reason="non windows test") -mark_dont_run_on_posix = pytest.mark.skipif(os.name == 'posix', reason="non posix test") +mark_dont_run_on_windows = pytest.mark.skipif(os.name == "nt", reason="non windows test") +mark_dont_run_on_posix = pytest.mark.skipif(os.name == "posix", reason="non posix test") def pytest_configure(): - if 'TOXENV' in os.environ: - del os.environ['TOXENV'] - if 'HUDSON_URL' in os.environ: - del os.environ['HUDSON_URL'] + if "TOXENV" in os.environ: + del os.environ["TOXENV"] + if "HUDSON_URL" in os.environ: + del os.environ["HUDSON_URL"] def pytest_addoption(parser): - parser.addoption("--no-network", action="store_true", dest="no_network", - help="don't run tests requiring network") + parser.addoption( + "--no-network", + action="store_true", + dest="no_network", + help="don't run tests requiring network", + ) def pytest_report_header(): - return "tox comes from: {}".format(repr(tox.__file__)) + return "tox comes from: {!r}".format(tox.__file__) @pytest.fixture @@ -43,6 +47,7 @@ def work_in_clean_dir(tmpdir): @pytest.fixture(name="newconfig") def create_new_config_file(tmpdir): + def create_new_config_file_(args, source=None, plugins=()): if source is None: source = args @@ -52,6 +57,7 @@ def create_new_config_file_(args, source=None, plugins=()): p.write(s) with tmpdir.as_cwd(): return parseconfig(args, plugins=plugins) + return create_new_config_file_ @@ -62,7 +68,7 @@ def cmd(request, capfd, monkeypatch): request.addfinalizer(py.path.local().chdir) def run(*argv): - key = str(b'PYTHONPATH') + key = str(b"PYTHONPATH") python_paths = (i for i in (str(os.getcwd()), os.getenv(key)) if i) monkeypatch.setenv(key, os.pathsep.join(python_paths)) with RunResult(capfd, argv) as result: @@ -74,10 +80,12 @@ def run(*argv): except OSError as e: result.ret = e.errno return result + yield run class RunResult: + def __init__(self, capfd, args): self._capfd = capfd self.args = args @@ -101,11 +109,13 @@ def outlines(self): return self.out.splitlines() def __repr__(self): - return 'RunResult(ret={}, args={}, out=\n{}\n, err=\n{})'.format( - self.ret, ' '.join(str(i) for i in self.args), self.out, self.err) + return "RunResult(ret={}, args={}, out=\n{}\n, err=\n{})".format( + self.ret, " ".join(str(i) for i in self.args), self.out, self.err + ) class ReportExpectMock: + def __init__(self, session): self._calls = [] self._index = -1 @@ -117,13 +127,13 @@ def clear(self): def __getattr__(self, name): if name[0] == "_": raise AttributeError(name) - elif name == 'verbosity': + elif name == "verbosity": # FIXME: special case for property on Reporter class, may it be generalized? return 0 def generic_report(*args, **_): self._calls.append((name,) + args) - print("%s" % (self._calls[-1],)) + print("{}".format(self._calls[-1])) return generic_report @@ -138,8 +148,10 @@ def getnext(self, cat): return call newindex += 1 raise LookupError( - "looking for %r, no reports found at >=%d in %r" % - (cat, self._index + 1, self._calls)) + "looking for {!r}, no reports found at >={:d} in {!r}".format( + cat, self._index + 1, self._calls + ) + ) def expect(self, cat, messagepattern="*", invert=False): __tracebackhide__ = True @@ -154,19 +166,23 @@ def expect(self, cat, messagepattern="*", invert=False): lmsg = str(lmsg).replace("\n", " ") if fnmatch(lmsg, messagepattern): if invert: - raise AssertionError("found %s(%r), didn't expect it" % - (cat, messagepattern)) + raise AssertionError( + "found {}({!r}), didn't expect it".format(cat, messagepattern) + ) return if not invert: raise AssertionError( - "looking for %s(%r), no reports found at >=%d in %r" % - (cat, messagepattern, self._index + 1, self._calls)) + "looking for {}({!r}), no reports found at >={:d} in {!r}".format( + cat, messagepattern, self._index + 1, self._calls + ) + ) def not_expect(self, cat, messagepattern="*"): return self.expect(cat, messagepattern, invert=True) class pcallMock: + def __init__(self, args, cwd, env, stdout, stderr, shell): self.arg0 = args[0] self.args = args[1:] @@ -186,7 +202,9 @@ def wait(self): @pytest.fixture(name="mocksession") def create_mocksession(request): + class MockSession(Session): + def __init__(self): self._clearmocks() self.config = request.getfixturevalue("newconfig")([], "") @@ -208,14 +226,17 @@ def popen(self, args, cwd, shell=None, stdout=None, stderr=None, env=None, **_): pm = pcallMock(args, cwd, env, stdout, stderr, shell) self._pcalls.append(pm) return pm + return MockSession() @pytest.fixture def newmocksession(mocksession, newconfig): + def newmocksession_(args, source, plugins=()): mocksession.config = newconfig(args, source, plugins=plugins) return mocksession + return newmocksession_ @@ -223,8 +244,7 @@ def getdecoded(out): try: return out.decode("utf-8") except UnicodeDecodeError: - return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % ( - py.io.saferepr(out),) + return "INTERNAL not-utf8-decodeable, truncated string:\n{}".format(py.io.saferepr(out)) @pytest.fixture @@ -252,11 +272,12 @@ def initproj(tmpdir): name.egg-info/ # created later on package build setup.py """ + def initproj_(nameversion, filedefs=None, src_root="."): if filedefs is None: filedefs = {} if not src_root: - src_root = '.' + src_root = "." if isinstance(nameversion, six.string_types): parts = nameversion.split(str("-")) if len(parts) == 1: @@ -266,32 +287,44 @@ def initproj_(nameversion, filedefs=None, src_root="."): name, version = nameversion base = tmpdir.join(name) src_root_path = _path_join(base, src_root) - assert base == src_root_path or src_root_path.relto(base), ( - '`src_root` must be the constructed project folder or its direct ' - 'or indirect subfolder') + assert ( + base == src_root_path or src_root_path.relto(base) + ), "`src_root` must be the constructed project folder or its direct or indirect subfolder" + base.ensure(dir=1) create_files(base, filedefs) - if not _filedefs_contains(base, filedefs, 'setup.py'): - create_files(base, {'setup.py': """ + if not _filedefs_contains(base, filedefs, "setup.py"): + create_files( + base, + { + "setup.py": """ from setuptools import setup, find_packages setup( - name='%(name)s', - description='%(name)s project', - version='%(version)s', + name='{name}', + description='{name} project', + version='{version}', license='MIT', platforms=['unix', 'win32'], - packages=find_packages('%(src_root)s'), - package_dir={'':'%(src_root)s'}, + packages=find_packages('{src_root}'), + package_dir={{'':'{src_root}'}}, ) - """ % locals()}) + """.format( + **locals() + ) + }, + ) if not _filedefs_contains(base, filedefs, src_root_path.join(name)): - create_files(src_root_path, {name: {'__init__.py': '__version__ = %r' % version}}) - manifestlines = ["include %s" % p.relto(base) - for p in base.visit(lambda x: x.check(file=1))] + create_files( + src_root_path, {name: {"__init__.py": "__version__ = {!r}".format(version)}} + ) + manifestlines = [ + "include {}".format(p.relto(base)) for p in base.visit(lambda x: x.check(file=1)) + ] create_files(base, {"MANIFEST.in": "\n".join(manifestlines)}) - print("created project in %s" % base) + print("created project in {}".format(base)) base.chdir() return base + return initproj_ diff --git a/tox/_quickstart.py b/tox/_quickstart.py index 01269677f..bb6c229c6 100644 --- a/tox/_quickstart.py +++ b/tox/_quickstart.py @@ -43,13 +43,14 @@ import codecs import os import sys +import textwrap import six import tox -ALTERNATIVE_CONFIG_NAME = 'tox-generated.ini' -QUICKSTART_CONF = '''\ +ALTERNATIVE_CONFIG_NAME = "tox-generated.ini" +QUICKSTART_CONF = """\ # tox (https://tox.readthedocs.io/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" @@ -63,7 +64,7 @@ {deps} commands = {commands} -''' +""" class ValidationError(Exception): @@ -77,24 +78,24 @@ def nonempty(x): def choice(*l): + def val(x): if x not in l: - raise ValidationError('Please enter one of %s.' % ', '.join(l)) + raise ValidationError("Please enter one of {}.".format(", ".join(l))) return x return val def boolean(x): - if x.upper() not in ('Y', 'YES', 'N', 'NO'): + if x.upper() not in ("Y", "YES", "N", "NO"): raise ValidationError("Please enter either 'y' or 'n'.") - return x.upper() in ('Y', 'YES') + return x.upper() in ("Y", "YES") def suffix(x): - if not (x[0:1] == '.' and len(x) > 1): - raise ValidationError("Please enter a file suffix, " - "e.g. '.rst' or '.txt'.") + if not (x[0:1] == "." and len(x) > 1): + raise ValidationError("Please enter a file suffix, e.g. '.rst' or '.txt'.") return x @@ -109,36 +110,39 @@ def list_modificator(answer, existing=None): existing = [existing] if not answer: return existing - existing.extend([t.strip() for t in answer.split(',') if t.strip()]) + existing.extend([t.strip() for t in answer.split(",") if t.strip()]) return existing def do_prompt(map_, key, text, default=None, validator=nonempty, modificator=None): while True: - prompt = '> %s [%s]: ' % (text, default) if default else '> %s: ' % text + prompt = "> {} [{}]: ".format(text, default) if default else "> {}: ".format(text) answer = six.moves.input(prompt) if default and not answer: answer = default # FIXME use six instead of self baked solution + # noinspection PyUnresolvedReferences if sys.version_info < (3,) and not isinstance(answer, unicode): # noqa # for Python 2.x, try to get a Unicode string out of it - if answer.decode('ascii', 'replace').encode('ascii', 'replace') != answer: - term_encoding = getattr(sys.stdin, 'encoding', None) + if answer.decode("ascii", "replace").encode("ascii", "replace") != answer: + term_encoding = getattr(sys.stdin, "encoding", None) if term_encoding: answer = answer.decode(term_encoding) else: - print('* Note: non-ASCII characters entered but terminal encoding unknown ' - '-> assuming UTF-8 or Latin-1.') + print( + "* Note: non-ASCII characters entered but terminal encoding unknown" + " -> assuming UTF-8 or Latin-1." + ) try: - answer = answer.decode('utf-8') + answer = answer.decode("utf-8") except UnicodeDecodeError: - answer = answer.decode('latin1') + answer = answer.decode("latin1") if validator: try: answer = validator(answer) except ValidationError: err = sys.exc_info()[1] - print('* ' + str(err)) + print("* " + str(err)) continue break map_[key] = modificator(answer, map_.get(key)) if modificator else answer @@ -146,105 +150,144 @@ def do_prompt(map_, key, text, default=None, validator=nonempty, modificator=Non def ask_user(map_): """modify *map_* in place by getting info from the user.""" - print('Welcome to the tox %s quickstart utility.' % tox.__version__) - print('This utility will ask you a few questions and then generate a simple configuration ' - 'file to help get you started using tox.\n' - 'Please enter values for the following settings (just press Enter to accept a ' - 'default value, if one is given in brackets).\n') - print('What Python versions do you want to test against?\n' - ' [1] %s\n' - ' [2] py27, %s\n' - ' [3] (All versions) %s\n' - ' [4] Choose each one-by-one' % ( - tox.PYTHON.CURRENT_RELEASE_ENV, tox.PYTHON.CURRENT_RELEASE_ENV, - ', '.join(tox.PYTHON.QUICKSTART_PY_ENVS))) - do_prompt(map_, 'canned_pyenvs', 'Enter the number of your choice', - default='3', validator=choice('1', '2', '3', '4')) - if map_['canned_pyenvs'] == '1': + print("Welcome to the tox {} quickstart utility.".format(tox.__version__)) + print( + "This utility will ask you a few questions and then generate a simple configuration " + "file to help get you started using tox.\n" + "Please enter values for the following settings (just press Enter to accept a " + "default value, if one is given in brackets).\n" + ) + print( + textwrap.dedent( + """What Python versions do you want to test against? + [1] {} + [2] py27, {} + [3] (All versions) {} + [4] Choose each one-by-one""" + ).format( + tox.PYTHON.CURRENT_RELEASE_ENV, + tox.PYTHON.CURRENT_RELEASE_ENV, + ", ".join(tox.PYTHON.QUICKSTART_PY_ENVS), + ) + ) + do_prompt( + map_, + "canned_pyenvs", + "Enter the number of your choice", + default="3", + validator=choice("1", "2", "3", "4"), + ) + if map_["canned_pyenvs"] == "1": map_[tox.PYTHON.CURRENT_RELEASE_ENV] = True - elif map_['canned_pyenvs'] == '2': - for pyenv in ('py27', tox.PYTHON.CURRENT_RELEASE_ENV): + elif map_["canned_pyenvs"] == "2": + for pyenv in ("py27", tox.PYTHON.CURRENT_RELEASE_ENV): map_[pyenv] = True - elif map_['canned_pyenvs'] == '3': + elif map_["canned_pyenvs"] == "3": for pyenv in tox.PYTHON.QUICKSTART_PY_ENVS: map_[pyenv] = True - elif map_['canned_pyenvs'] == '4': + elif map_["canned_pyenvs"] == "4": for pyenv in tox.PYTHON.QUICKSTART_PY_ENVS: if pyenv not in map_: - do_prompt(map_, pyenv, 'Test your project with %s (Y/n)' % pyenv, 'Y', - validator=boolean) - print('What command should be used to test your project? Examples:\n' - ' - pytest\n' - ' - python -m unittest discover\n' - ' - python setup.py test\n' - ' - trial package.module\n') - do_prompt(map_, 'commands', 'Type the command to run your tests', - default='pytest', modificator=list_modificator) - print('What extra dependencies do your tests have?') - map_['deps'] = get_default_deps(map_['commands']) - if map_['deps']: - print("default dependencies are: %s" % map_['deps']) - do_prompt(map_, 'deps', 'Comma-separated list of dependencies', - validator=None, modificator=list_modificator) + do_prompt( + map_, + pyenv, + "Test your project with {} (Y/n)".format(pyenv), + "Y", + validator=boolean, + ) + print( + textwrap.dedent( + """What command should be used to test your project? Examples:\ + - pytest\n" + - python -m unittest discover + - python setup.py test + - trial package.module""" + ) + ) + do_prompt( + map_, + "commands", + "Type the command to run your tests", + default="pytest", + modificator=list_modificator, + ) + print("What extra dependencies do your tests have?") + map_["deps"] = get_default_deps(map_["commands"]) + if map_["deps"]: + print("default dependencies are: {}".format(map_["deps"])) + do_prompt( + map_, + "deps", + "Comma-separated list of dependencies", + validator=None, + modificator=list_modificator, + ) def get_default_deps(commands): - if commands and any(c in str(commands) for c in ['pytest', 'py.test']): - return ['pytest'] - if 'trial' in commands: - return ['twisted'] + if commands and any(c in str(commands) for c in ["pytest", "py.test"]): + return ["pytest"] + if "trial" in commands: + return ["twisted"] return [] def post_process_input(map_): envlist = [env for env in tox.PYTHON.QUICKSTART_PY_ENVS if map_.get(env) is True] - map_['envlist'] = ', '.join(envlist) - map_['commands'] = '\n '.join([cmd.strip() for cmd in map_['commands']]) - map_['deps'] = '\n '.join([dep.strip() for dep in set(map_['deps'])]) + map_["envlist"] = ", ".join(envlist) + map_["commands"] = "\n ".join([cmd.strip() for cmd in map_["commands"]]) + map_["deps"] = "\n ".join([dep.strip() for dep in set(map_["deps"])]) def generate(map_): """Generate project based on values in *d*.""" - dpath = map_.get('path', os.getcwd()) + dpath = map_.get("path", os.getcwd()) altpath = os.path.join(dpath, ALTERNATIVE_CONFIG_NAME) while True: - name = map_.get('name', tox.INFO.DEFAULT_CONFIG_NAME) + name = map_.get("name", tox.INFO.DEFAULT_CONFIG_NAME) targetpath = os.path.join(dpath, name) if not os.path.isfile(targetpath): break - do_prompt(map_, 'name', '%s exists - choose an alternative' % targetpath, altpath) - with codecs.open(targetpath, 'w', encoding='utf-8') as f: + do_prompt(map_, "name", "{} exists - choose an alternative".format(targetpath), altpath) + with codecs.open(targetpath, "w", encoding="utf-8") as f: f.write(prepare_content(QUICKSTART_CONF.format(**map_))) - print('Finished: %s has been created. For information on this file, ' - 'see https://tox.readthedocs.io/en/latest/config.html\n' - 'Execute `tox` to test your project.' % targetpath) + print( + "Finished: {} has been created. For information on this file, " + "see https://tox.readthedocs.io/en/latest/config.html\n" + "Execute `tox` to test your project.".format(targetpath) + ) def prepare_content(content): - return '\n'.join([line.rstrip() for line in content.split("\n")]) + return "\n".join([line.rstrip() for line in content.split("\n")]) def parse_args(): parser = argparse.ArgumentParser( - description='Command-line script to quickly tox config file for a Python project.') + description="Command-line script to quickly tox config file for a Python project." + ) parser.add_argument( - 'root', type=str, nargs='?', default='.', - help='Custom root directory to write config to. Defaults to current directory.') - parser.add_argument('--version', action='version', version='%(prog)s ' + tox.__version__) + "root", + type=str, + nargs="?", + default=".", + help="Custom root directory to write config to. Defaults to current directory.", + ) + parser.add_argument("--version", action="version", version="%(prog)s " + tox.__version__) return parser.parse_args() def main(): args = parse_args() - map_ = {'path': args.root} + map_ = {"path": args.root} try: ask_user(map_) except (KeyboardInterrupt, EOFError): - print('\n[Interrupted.]') + print("\n[Interrupted.]") return 1 post_process_input(map_) generate(map_) -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/tox/_verlib.py b/tox/_verlib.py index 39294695a..1c9c7f315 100644 --- a/tox/_verlib.py +++ b/tox/_verlib.py @@ -40,9 +40,10 @@ class HugeMajorVersionNumError(IrrationalVersionError): # | # 'dev' < 'f' ----------------------------------------------/ # Other letters would do, but 'f' for 'final' is kind of nice. -FINAL_MARKER = ('f',) +FINAL_MARKER = ("f",) -VERSION_RE = re.compile(r''' +VERSION_RE = re.compile( + r""" ^ (?P\d+\.\d+) # minimum 'N.N' (?P(?:\.\d+)*) # any number of extra '.N' segments @@ -52,7 +53,9 @@ class HugeMajorVersionNumError(IrrationalVersionError): (?P\d+(?:\.\d+)*) )? (?P(\.post(?P\d+))?(\.dev(?P\d+))?)? - $''', re.VERBOSE) + $""", + re.VERBOSE, +) class NormalizedVersion(object): @@ -94,8 +97,7 @@ def __init__(self, s, error_on_huge_major_num=True): self._parse(s, error_on_huge_major_num) @classmethod - def from_parts(cls, version, prerelease=FINAL_MARKER, - devpost=FINAL_MARKER): + def from_parts(cls, version, prerelease=FINAL_MARKER, devpost=FINAL_MARKER): return cls(cls.parts_to_str((version, prerelease, devpost))) def _parse(self, s, error_on_huge_major_num=True): @@ -108,44 +110,44 @@ def _parse(self, s, error_on_huge_major_num=True): parts = [] # main version - block = self._parse_numdots(groups['version'], s, False, 2) - extraversion = groups.get('extraversion') - if extraversion not in ('', None): + block = self._parse_numdots(groups["version"], s, False, 2) + extraversion = groups.get("extraversion") + if extraversion not in ("", None): block += self._parse_numdots(extraversion[1:], s) parts.append(tuple(block)) # prerelease - prerel = groups.get('prerel') + prerel = groups.get("prerel") if prerel is not None: block = [prerel] - block += self._parse_numdots(groups.get('prerelversion'), s, - pad_zeros_length=1) + block += self._parse_numdots(groups.get("prerelversion"), s, pad_zeros_length=1) parts.append(tuple(block)) else: parts.append(FINAL_MARKER) # postdev - if groups.get('postdev'): - post = groups.get('post') - dev = groups.get('dev') + if groups.get("postdev"): + post = groups.get("post") + dev = groups.get("dev") postdev = [] if post is not None: - postdev.extend([FINAL_MARKER[0], 'post', int(post)]) + postdev.extend([FINAL_MARKER[0], "post", int(post)]) if dev is None: postdev.append(FINAL_MARKER[0]) if dev is not None: - postdev.extend(['dev', int(dev)]) + postdev.extend(["dev", int(dev)]) parts.append(tuple(postdev)) else: parts.append(FINAL_MARKER) self.parts = tuple(parts) if error_on_huge_major_num and self.parts[0][0] > 1980: raise HugeMajorVersionNumError( - "huge major version number, %r, " - "which might cause future problems: %r" % (self.parts[0][0], s)) + "huge major version number, {!r}, which might cause future problems: {!r}".format( + self.parts[0][0], s + ) + ) - def _parse_numdots(self, s, full_ver_str, drop_trailing_zeros=True, - pad_zeros_length=0): + def _parse_numdots(self, s, full_ver_str, drop_trailing_zeros=True, pad_zeros_length=0): """Parse 'N.N.N' sequences, return a list of ints. @param s {str} 'N.N.N..." sequence to be parsed @@ -158,10 +160,12 @@ def _parse_numdots(self, s, full_ver_str, drop_trailing_zeros=True, """ nums = [] for n in s.split("."): - if len(n) > 1 and n[0] == '0': + if len(n) > 1 and n[0] == "0": raise IrrationalVersionError( - "cannot have leading zero in " - "version number segment: '%s' in %r" % (n, full_ver_str)) + "cannot have leading zero in version number segment: '{}' in {!r}".format( + n, full_ver_str + ) + ) nums.append(int(n)) if drop_trailing_zeros: while nums and nums[-1] == 0: @@ -178,27 +182,28 @@ def parts_to_str(cls, parts): """Transform a version expressed in tuple into its string representation.""" # FIXME XXX This doesn't check for invalid tuples main, prerel, postdev = parts - s = '.'.join(str(v) for v in main) + s = ".".join(str(v) for v in main) if prerel is not FINAL_MARKER: s += prerel[0] - s += '.'.join(str(v) for v in prerel[1:]) + s += ".".join(str(v) for v in prerel[1:]) if postdev and postdev is not FINAL_MARKER: - if postdev[0] == 'f': + if postdev[0] == "f": postdev = postdev[1:] i = 0 while i < len(postdev): if i % 2 == 0: - s += '.' + s += "." s += str(postdev[i]) i += 1 return s def __repr__(self): - return "%s('%s')" % (self.__class__.__name__, self) + return "{}('{}')".format(self.__class__.__name__, self) def _cannot_compare(self, other): - raise TypeError("cannot compare %s and %s" - % (type(self).__name__, type(other).__name__)) + raise TypeError( + "cannot compare {} and {}".format(type(self).__name__, type(other).__name__) + ) def __eq__(self, other): if not isinstance(other, NormalizedVersion): diff --git a/tox/config.py b/tox/config.py index 67d3fbd7b..b3b8a843a 100755 --- a/tox/config.py +++ b/tox/config.py @@ -16,9 +16,8 @@ import py import tox -from tox.interpreters import Interpreters from tox._verlib import NormalizedVersion - +from tox.interpreters import Interpreters hookimpl = tox.hookimpl """DEPRECATED - REMOVE - this is left for compatibility with plugins importing this from here. @@ -36,6 +35,7 @@ def get_plugin_manager(plugins=()): # initialize plugin manager import tox.venv + pm = pluggy.PluginManager("tox") pm.add_hookspecs(tox.hookspecs) pm.register(tox.config) @@ -51,9 +51,9 @@ def get_plugin_manager(plugins=()): class Parser: """Command line and ini-parser control object.""" + def __init__(self): - self.argparser = argparse.ArgumentParser( - description="tox options", add_help=False) + self.argparser = argparse.ArgumentParser(description="tox options", add_help=False) self._testenv_attr = [] def add_argument(self, *args, **kwargs): @@ -99,6 +99,7 @@ def _format_help(self): class VenvAttribute: + def __init__(self, name, type, default, help, postprocess): self.name = name self.type = type @@ -129,11 +130,11 @@ def postprocess(self, testenv_config, value): # in case of a short option, we remove the space for option in tox.PIP.INSTALL_SHORT_OPTIONS_ARGUMENT: if name.startswith(option): - name = '%s%s' % (option, name[len(option):].strip()) + name = "{}{}".format(option, name[len(option):].strip()) # in case of a long option, we add an equal sign for option in tox.PIP.INSTALL_LONG_OPTIONS_ARGUMENT: - if name.startswith(option + ' '): - name = '%s=%s' % (option, name[len(option):].strip()) + if name.startswith(option + " "): + name = "{}={}".format(option, name[len(option):].strip()) name = self._replace_forced_dep(name, config) deps.append(DepConfig(name, ixserver)) return deps @@ -193,9 +194,10 @@ class InstallcmdOption: help = "install command for dependencies and package under test." def postprocess(self, testenv_config, value): - if '{packages}' not in value: + if "{packages}" not in value: raise tox.exception.ConfigError( - "'install_command' must contain '{packages}' substitution") + "'install_command' must contain '{packages}' substitution" + ) return value @@ -226,18 +228,17 @@ def parseconfig(args, plugins=()): inipath = py.path.local(basename) elif os.path.isdir(basename): # Assume 'tox.ini' filename if directory was passed - inipath = py.path.local(os.path.join(basename, 'tox.ini')) + inipath = py.path.local(os.path.join(basename, "tox.ini")) else: for path in py.path.local().parts(reverse=True): inipath = path.join(basename) if inipath.check(): break else: - inipath = py.path.local().join('setup.cfg') + inipath = py.path.local().join("setup.cfg") if not inipath.check(): helpoptions = option.help or option.helpini - feedback("toxini file %r not found" % (basename), - sysexit=not helpoptions) + feedback("toxini file {!r} not found".format(basename), sysexit=not helpoptions) if helpoptions: return config @@ -246,7 +247,7 @@ def parseconfig(args, plugins=()): except tox.exception.InterpreterNotFound: exn = sys.exc_info()[1] # Use stdout to match test expectations - print("ERROR: " + str(exn)) + print("ERROR: {}".format(exn)) # post process config object pm.hook.tox_configure(config=config) @@ -261,15 +262,14 @@ def feedback(msg, sysexit=False): def get_version_info(pm): - out = ["%s imported from %s" % (tox.__version__, tox.__file__)] + out = ["{} imported from {}".format(tox.__version__, tox.__file__)] plugin_dist_info = pm.list_plugin_distinfo() if plugin_dist_info: - out.append('registered plugins:') + out.append("registered plugins:") for mod, egg_info in plugin_dist_info: - source = getattr(mod, '__file__', repr(mod)) - out.append(" %s-%s at %s" % ( - egg_info.project_name, egg_info.version, source)) - return '\n'.join(out) + source = getattr(mod, "__file__", repr(mod)) + out.append(" {}-{} at {}".format(egg_info.project_name, egg_info.version, source)) + return "\n".join(out) class SetenvDict(object): @@ -282,7 +282,7 @@ def __init__(self, definitions, reader): self._lookupstack = [] def __repr__(self): - return "%s: %s" % (self.__class__.__name__, self.definitions) + return "{}: {}".format(self.__class__.__name__, self.definitions) def __contains__(self, name): return name in self.definitions @@ -321,100 +321,189 @@ def __setitem__(self, name, value): @tox.hookimpl def tox_addoption(parser): # formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("--version", action="store_true", dest="version", - help="report version information to stdout.") - parser.add_argument("-h", "--help", action="store_true", dest="help", - help="show help about options") - parser.add_argument("--help-ini", "--hi", action="store_true", dest="helpini", - help="show help about ini-names") - parser.add_argument("-v", action='count', dest="verbose_level", default=0, - help="increase verbosity of reporting output. -vv mode turns off " - "output redirection for package installation") - parser.add_argument("-q", action="count", dest="quiet_level", default=0, - help="progressively silence reporting output.") - parser.add_argument("--showconfig", action="store_true", - help="show configuration information for all environments. ") - parser.add_argument("-l", "--listenvs", action="store_true", - dest="listenvs", help="show list of test environments " - "(with description if verbose)") - parser.add_argument("-a", "--listenvs-all", action="store_true", - dest="listenvs_all", - help="show list of all defined environments" - "(with description if verbose)") - parser.add_argument("-c", action="store", default="tox.ini", - dest="configfile", - help="config file name or directory with 'tox.ini' file.") - parser.add_argument("-e", action="append", dest="env", - metavar="envlist", - help="work against specified environments (ALL selects all).") - parser.add_argument("--notest", action="store_true", dest="notest", - help="skip invoking test commands.") - parser.add_argument("--sdistonly", action="store_true", dest="sdistonly", - help="only perform the sdist packaging activity.") - parser.add_argument("--installpkg", action="store", default=None, - metavar="PATH", - help="use specified package for installation into venv, instead of " - "creating an sdist.") - parser.add_argument("--develop", action="store_true", dest="develop", - help="install package in the venv using 'setup.py develop' via " - "'pip -e .'") - parser.add_argument('-i', '--index-url', action="append", - dest="indexurl", metavar="URL", - help="set indexserver url (if URL is of form name=url set the " - "url for the 'name' indexserver, specifically)") - parser.add_argument("--pre", action="store_true", dest="pre", - help="install pre-releases and development versions of dependencies. " - "This will pass the --pre option to install_command " - "(pip by default).") - parser.add_argument("-r", "--recreate", action="store_true", - dest="recreate", - help="force recreation of virtual environments") - parser.add_argument("--result-json", action="store", - dest="resultjson", metavar="PATH", - help="write a json file with detailed information " - "about all commands and results involved.") + parser.add_argument( + "--version", + action="store_true", + dest="version", + help="report version information to stdout.", + ) + parser.add_argument( + "-h", "--help", action="store_true", dest="help", help="show help about options" + ) + parser.add_argument( + "--help-ini", "--hi", action="store_true", dest="helpini", help="show help about ini-names" + ) + parser.add_argument( + "-v", + action="count", + dest="verbose_level", + default=0, + help="increase verbosity of reporting output. -vv mode turns off " + "output redirection for package installation", + ) + parser.add_argument( + "-q", + action="count", + dest="quiet_level", + default=0, + help="progressively silence reporting output.", + ) + parser.add_argument( + "--showconfig", + action="store_true", + help="show configuration information for all environments. ", + ) + parser.add_argument( + "-l", + "--listenvs", + action="store_true", + dest="listenvs", + help="show list of test environments (with description if verbose)", + ) + parser.add_argument( + "-a", + "--listenvs-all", + action="store_true", + dest="listenvs_all", + help="show list of all defined environments (with description if verbose)", + ) + parser.add_argument( + "-c", + action="store", + default="tox.ini", + dest="configfile", + help="config file name or directory with 'tox.ini' file.", + ) + parser.add_argument( + "-e", + action="append", + dest="env", + metavar="envlist", + help="work against specified environments (ALL selects all).", + ) + parser.add_argument( + "--notest", action="store_true", dest="notest", help="skip invoking test commands." + ) + parser.add_argument( + "--sdistonly", + action="store_true", + dest="sdistonly", + help="only perform the sdist packaging activity.", + ) + parser.add_argument( + "--installpkg", + action="store", + default=None, + metavar="PATH", + help="use specified package for installation into venv, instead of creating an sdist.", + ) + parser.add_argument( + "--develop", + action="store_true", + dest="develop", + help="install package in the venv using 'setup.py develop' via 'pip -e .'", + ) + parser.add_argument( + "-i", + "--index-url", + action="append", + dest="indexurl", + metavar="URL", + help="set indexserver url (if URL is of form name=url set the " + "url for the 'name' indexserver, specifically)", + ) + parser.add_argument( + "--pre", + action="store_true", + dest="pre", + help="install pre-releases and development versions of dependencies. " + "This will pass the --pre option to install_command " + "(pip by default).", + ) + parser.add_argument( + "-r", + "--recreate", + action="store_true", + dest="recreate", + help="force recreation of virtual environments", + ) + parser.add_argument( + "--result-json", + action="store", + dest="resultjson", + metavar="PATH", + help="write a json file with detailed information " + "about all commands and results involved.", + ) # 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, 4294967295] " - "([1, 1024] on Windows). " - "Passing 'noset' suppresses this behavior.") - parser.add_argument("--force-dep", action="append", - metavar="REQ", default=None, - help="Forces a certain version of one of the dependencies " - "when configuring the virtual environment. REQ Examples " - "'pytest<2.7' or 'django>=1.6'.") - parser.add_argument("--sitepackages", action="store_true", - help="override sitepackages setting to True in all envs") - parser.add_argument("--alwayscopy", action="store_true", - help="override alwayscopy 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") + 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, 4294967295] " + "([1, 1024] on Windows). " + "Passing 'noset' suppresses this behavior.", + ) + parser.add_argument( + "--force-dep", + action="append", + metavar="REQ", + default=None, + help="Forces a certain version of one of the dependencies " + "when configuring the virtual environment. REQ Examples " + "'pytest<2.7' or 'django>=1.6'.", + ) + parser.add_argument( + "--sitepackages", + action="store_true", + help="override sitepackages setting to True in all envs", + ) + parser.add_argument( + "--alwayscopy", action="store_true", help="override alwayscopy 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" + ) parser.add_testenv_attribute( - name="envdir", type="path", default="{toxworkdir}/{envname}", + name="envdir", + type="path", + default="{toxworkdir}/{envname}", help="set venv directory -- be very careful when changing this as tox " - "will remove this directory when recreating an environment") + "will remove this directory when recreating an environment", + ) # add various core venv interpreter attributes 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 + setenv["PYTHONHASHSEED"] = config.hashseed return setenv parser.add_testenv_attribute( - name="setenv", type="dict_setenv", postprocess=setenv, - help="list of X=Y lines with environment variable settings") + name="setenv", + type="dict_setenv", + postprocess=setenv, + help="list of X=Y lines with environment variable settings", + ) def basepython_default(testenv_config, value): if value is None: @@ -425,47 +514,65 @@ def basepython_default(testenv_config, value): return str(value) parser.add_testenv_attribute( - name="basepython", type="string", default=None, postprocess=basepython_default, - help="executable name or path of interpreter used to create a " - "virtual test environment.") + name="basepython", + type="string", + default=None, + postprocess=basepython_default, + help="executable name or path of interpreter used to create a virtual test environment.", + ) def merge_description(testenv_config, value): """the reader by default joins generated description with new line, replace new line with space""" - return value.replace('\n', ' ') + return value.replace("\n", " ") parser.add_testenv_attribute( - name="description", type="string", default='', postprocess=merge_description, - help="short description of this environment") + name="description", + type="string", + default="", + postprocess=merge_description, + help="short description of this environment", + ) parser.add_testenv_attribute( - name="envtmpdir", type="path", default="{envdir}/tmp", - help="venv temporary directory") + name="envtmpdir", type="path", default="{envdir}/tmp", help="venv temporary directory" + ) parser.add_testenv_attribute( - name="envlogdir", type="path", default="{envdir}/log", - help="venv log directory") + name="envlogdir", type="path", default="{envdir}/log", help="venv log directory" + ) parser.add_testenv_attribute( - name="downloadcache", type="string", default=None, - help="(ignored) has no effect anymore, pip-8 uses local caching by default") + name="downloadcache", + type="string", + default=None, + help="(ignored) has no effect anymore, pip-8 uses local caching by default", + ) parser.add_testenv_attribute( - name="changedir", type="path", default="{toxinidir}", - help="directory to change to when running commands") + name="changedir", + type="path", + default="{toxinidir}", + help="directory to change to when running commands", + ) parser.add_testenv_attribute_obj(PosargsOption()) parser.add_testenv_attribute( - name="skip_install", type="bool", default=False, - help="Do not install the current package. This can be used when " - "you need the virtualenv management but do not want to install " - "the current package") + name="skip_install", + type="bool", + default=False, + help="Do not install the current package. This can be used when you need the virtualenv " + "management but do not want to install the current package", + ) parser.add_testenv_attribute( - name="ignore_errors", type="bool", default=False, - help="if set to True all commands will be executed irrespective of their " - "result error status.") + name="ignore_errors", + type="bool", + default=False, + help="if set to True all commands will be executed irrespective of their result error " + "status.", + ) def recreate(testenv_config, value): if testenv_config.config.option.recreate: @@ -473,18 +580,18 @@ def recreate(testenv_config, value): return value parser.add_testenv_attribute( - name="recreate", type="bool", default=False, postprocess=recreate, - help="always recreate this test environment.") + name="recreate", + type="bool", + default=False, + postprocess=recreate, + help="always recreate this test environment.", + ) def passenv(testenv_config, value): # Flatten the list to deal with space-separated values. - value = list( - itertools.chain.from_iterable( - [x.split(' ') for x in value])) + value = list(itertools.chain.from_iterable([x.split(" ") for x in value])) - passenv = { - "PATH", "PIP_INDEX_URL", "LANG", "LANGUAGE", "LD_LIBRARY_PATH" - } + passenv = {"PATH", "PIP_INDEX_URL", "LANG", "LANGUAGE", "LD_LIBRARY_PATH"} # read in global passenv settings p = os.environ.get("TOX_TESTENV_PASSENV", None) @@ -517,22 +624,29 @@ def passenv(testenv_config, value): return passenv parser.add_testenv_attribute( - name="passenv", type="line-list", postprocess=passenv, - help="environment variables needed during executing test commands " - "(taken from invocation environment). Note that tox always " - "passes through some basic environment variables which are " - "needed for basic functioning of the Python system. " - "See --showconfig for the eventual passenv setting.") + name="passenv", + type="line-list", + postprocess=passenv, + help="environment variables needed during executing test commands (taken from invocation " + "environment). Note that tox always passes through some basic environment variables " + "which are needed for basic functioning of the Python system. See --showconfig for the " + "eventual passenv setting.", + ) parser.add_testenv_attribute( - name="whitelist_externals", type="line-list", + name="whitelist_externals", + type="line-list", help="each lines specifies a path or basename for which tox will not warn " - "about it coming from outside the test environment.") + "about it coming from outside the test environment.", + ) parser.add_testenv_attribute( - name="platform", type="string", default=".*", + name="platform", + type="string", + default=".*", help="regular expression which must match against ``sys.platform``. " - "otherwise testenv will be skipped.") + "otherwise testenv will be skipped.", + ) def sitepackages(testenv_config, value): return testenv_config.config.option.sitepackages or value @@ -541,30 +655,45 @@ def alwayscopy(testenv_config, value): return testenv_config.config.option.alwayscopy or value parser.add_testenv_attribute( - name="sitepackages", type="bool", default=False, postprocess=sitepackages, + name="sitepackages", + type="bool", + default=False, + postprocess=sitepackages, help="Set to ``True`` if you want to create virtual environments that also " - "have access to globally installed packages.") + "have access to globally installed packages.", + ) parser.add_testenv_attribute( - name="alwayscopy", type="bool", default=False, postprocess=alwayscopy, + name="alwayscopy", + type="bool", + default=False, + postprocess=alwayscopy, help="Set to ``True`` if you want virtualenv to always copy files rather " - "than symlinking.") + "than symlinking.", + ) def pip_pre(testenv_config, value): return testenv_config.config.option.pre or value parser.add_testenv_attribute( - name="pip_pre", type="bool", default=False, postprocess=pip_pre, - help="If ``True``, adds ``--pre`` to the ``opts`` passed to " - "the install command. ") + name="pip_pre", + type="bool", + default=False, + postprocess=pip_pre, + help="If ``True``, adds ``--pre`` to the ``opts`` passed to the install command. ", + ) def develop(testenv_config, value): option = testenv_config.config.option return not option.installpkg and (value or option.develop) parser.add_testenv_attribute( - name="usedevelop", type="bool", postprocess=develop, default=False, - help="install package in develop/editable mode") + name="usedevelop", + type="bool", + postprocess=develop, + default=False, + help="install package in develop/editable mode", + ) parser.add_testenv_attribute_obj(InstallcmdOption()) @@ -572,27 +701,36 @@ def develop(testenv_config, value): name="list_dependencies_command", type="argv", default="pip freeze", - help="list dependencies for a virtual environment") + help="list dependencies for a virtual environment", + ) parser.add_testenv_attribute_obj(DepOption()) parser.add_testenv_attribute( - name="commands", type="argvlist", default="", - help="each line specifies a test command and can use substitution.") + name="commands", + type="argvlist", + default="", + help="each line specifies a test command and can use substitution.", + ) parser.add_testenv_attribute( - "ignore_outcome", type="bool", default=False, + "ignore_outcome", + type="bool", + default=False, help="if set to True a failing result of this testenv will not make " - "tox fail, only a warning will be produced") + "tox fail, only a warning will be produced", + ) parser.add_testenv_attribute( - "extras", type="line-list", - help="list of extras to install with the source distribution or " - "develop install") + "extras", + type="line-list", + help="list of extras to install with the source distribution or develop install", + ) class Config(object): """Global Tox config object.""" + def __init__(self, pluginmanager, option, interpreters): self.envconfigs = {} """Mapping envname -> envconfig""" @@ -616,6 +754,7 @@ class TestenvConfig: In addition to some core attributes/properties this config object holds all per-testenv ini attributes as attributes, see "tox --help-ini" for an overview. """ + def __init__(self, envname, config, factors, reader): #: test environment name self.envname = envname @@ -634,9 +773,7 @@ def __init__(self, envname, config, factors, reader): def get_envbindir(self): """Path to directory where scripts/binaries reside.""" - if (tox.INFO.IS_WIN and - "jython" not in self.basepython and - "pypy" not in self.basepython): + if tox.INFO.IS_WIN and "jython" not in self.basepython and "pypy" not in self.basepython: return self.envdir.join("Scripts") else: return self.envdir.join("bin") @@ -663,9 +800,7 @@ def get_envsitepackagesdir(self): NOTE: Only available during execution, not during parsing. """ - x = self.config.interpreters.get_sitepackagesdir( - info=self.python_info, - envdir=self.envdir) + x = self.config.interpreters.get_sitepackagesdir(info=self.python_info, envdir=self.envdir) return x @property @@ -676,13 +811,15 @@ def python_info(self): def getsupportedinterpreter(self): if tox.INFO.IS_WIN and self.basepython and "jython" in self.basepython: raise tox.exception.UnsupportedInterpreter( - "Jython/Windows does not support installing scripts") + "Jython/Windows does not support installing scripts" + ) info = self.config.interpreters.get_info(envconfig=self) if not info.executable: raise tox.exception.InterpreterNotFound(self.basepython) if not info.version_info: raise tox.exception.InvocationError( - 'Failed to get version_info for %s: %s' % (info.name, info.err)) + "Failed to get version_info for {}: {}".format(info.name, info.err) + ) return info.executable @@ -704,6 +841,7 @@ def make_hashseed(): class parseini: + def __init__(self, config, inipath): config.toxinipath = inipath config.toxinidir = config.toxinipath.dirpath() @@ -712,14 +850,15 @@ def __init__(self, config, inipath): config._cfg = self._cfg self.config = config - if inipath.basename == 'setup.cfg': - prefix = 'tox' + if inipath.basename == "setup.cfg": + prefix = "tox" else: prefix = None ctxname = getcontextname() if ctxname == "jenkins": - reader = SectionReader("tox:jenkins", self._cfg, prefix=prefix, - fallbacksections=['tox']) + reader = SectionReader( + "tox:jenkins", self._cfg, prefix=prefix, fallbacksections=["tox"] + ) distshare_default = "{toxworkdir}/distshare" elif not ctxname: reader = SectionReader("tox", self._cfg, prefix=prefix) @@ -729,14 +868,13 @@ def __init__(self, config, inipath): if config.option.hashseed is None: hashseed = make_hashseed() - elif config.option.hashseed == 'noset': + elif config.option.hashseed == "noset": hashseed = None else: hashseed = config.option.hashseed config.hashseed = hashseed - reader.addsubstitutions(toxinidir=config.toxinidir, - homedir=config.homedir) + reader.addsubstitutions(toxinidir=config.toxinidir, homedir=config.homedir) # As older versions of tox may have bugs or incompatibilities that # prevent parsing of tox.ini this must be the first thing checked. config.minversion = reader.getstring("minversion", None) @@ -745,19 +883,20 @@ def __init__(self, config, inipath): toxversion = NormalizedVersion(tox.__version__) if toxversion < minversion: raise tox.exception.MinVersionError( - "tox version is %s, required is at least %s" % ( - toxversion, minversion)) + "tox version is {}, required is at least {}".format(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 = \ - reader.getbool("skip_missing_interpreters", False) + config.option.skip_missing_interpreters = reader.getbool( + "skip_missing_interpreters", False + ) # determine indexserver dictionary - config.indexserver = {'default': IndexServerConfig('default')} + config.indexserver = {"default": IndexServerConfig("default")} prefix = "indexserver" for line in reader.getlist(prefix): name, url = map(lambda x: x.strip(), line.split("=", 1)) @@ -803,17 +942,19 @@ def __init__(self, config, inipath): stated_envlist = reader.getstring("envlist", replace=False) if stated_envlist: for env in _split_env(stated_envlist): - known_factors.update(env.split('-')) + known_factors.update(env.split("-")) # configure testenvs for name in all_envs: section = testenvprefix + name - factors = set(name.split('-')) + factors = set(name.split("-")) if section in self._cfg or factors <= known_factors: config.envconfigs[name] = self.make_envconfig(name, section, reader._subs, config) - all_develop = all(name in config.envconfigs and - config.envconfigs[name].usedevelop for name in config.envlist) + all_develop = all( + name in config.envconfigs and config.envconfigs[name].usedevelop + for name in config.envlist + ) config.skipsdist = reader.getbool("skipsdist", all_develop) @@ -821,18 +962,21 @@ def _list_section_factors(self, section): factors = set() if section in self._cfg: for _, value in self._cfg[section].items(): - exprs = re.findall(r'^([\w{}\.!,-]+)\:\s+', value, re.M) + exprs = re.findall(r"^([\w{}\.!,-]+)\:\s+", value, re.M) factors.update(*mapcat(_split_factor_expr_all, exprs)) return factors def make_envconfig(self, name, section, subs, config, replace=True): - factors = set(name.split('-')) - reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], - factors=factors) + factors = set(name.split("-")) + reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], factors=factors) tc = TestenvConfig(name, config, factors, reader) reader.addsubstitutions( - envname=name, envbindir=tc.get_envbindir, envsitepackagesdir=tc.get_envsitepackagesdir, - envpython=tc.get_envpython, **subs) + envname=name, + envbindir=tc.get_envbindir, + envsitepackagesdir=tc.get_envsitepackagesdir, + envpython=tc.get_envpython, + **subs + ) for env_attr in config._testenv_attr: atype = env_attr.type try: @@ -844,7 +988,7 @@ def make_envconfig(self, name, section, subs, config, replace=True): elif atype == "line-list": res = reader.getlist(env_attr.name, sep="\n") else: - raise ValueError("unknown type %r" % (atype,)) + raise ValueError("unknown type {!r}".format(atype)) if env_attr.postprocess: res = env_attr.postprocess(testenv_config=tc, value=res) except tox.exception.MissingSubstitution as e: @@ -856,37 +1000,39 @@ def make_envconfig(self, name, section, subs, config, replace=True): return tc def _getenvdata(self, reader): - envstr = self.config.option.env \ - or os.environ.get("TOXENV") \ - or reader.getstring("envlist", replace=False) \ - or [] - envlist = _split_env(envstr) + candidates = ( + self.config.option.env, + os.environ.get("TOXENV"), + reader.getstring("envlist", replace=False), + ) + env_str = next((i for i in candidates if i), []) + env_list = _split_env(env_str) # collect section envs - all_envs = set(envlist) - {"ALL"} + all_envs = set(env_list) - {"ALL"} for section in self._cfg: if section.name.startswith(testenvprefix): all_envs.add(section.name[len(testenvprefix):]) if not all_envs: all_envs.add("python") - if not envlist or "ALL" in envlist: - envlist = sorted(all_envs) + if not env_list or "ALL" in env_list: + env_list = sorted(all_envs) - return envlist, all_envs + return env_list, all_envs def _split_env(env): """if handed a list, action="append" was used for -e """ if not isinstance(env, list): - env = [e.split('#', 1)[0].strip() for e in env.split('\n')] - env = ','.join([e for e in env if e]) + env = [e.split("#", 1)[0].strip() for e in env.split("\n")] + env = ",".join([e for e in env if e]) env = [env] return mapcat(_expand_envstr, env) def _is_negated_factor(factor): - return factor.startswith('!') + return factor.startswith("!") def _base_factor_name(factor): @@ -894,12 +1040,11 @@ def _base_factor_name(factor): def _split_factor_expr(expr): + def split_single(e): - raw = e.split('-') - included = {_base_factor_name(factor) for factor in raw - if not _is_negated_factor(factor)} - excluded = {_base_factor_name(factor) for factor in raw - if _is_negated_factor(factor)} + raw = e.split("-") + included = {_base_factor_name(factor) for factor in raw if not _is_negated_factor(factor)} + excluded = {_base_factor_name(factor) for factor in raw if _is_negated_factor(factor)} return included, excluded partial_envs = _expand_envstr(expr) @@ -908,20 +1053,18 @@ def split_single(e): def _split_factor_expr_all(expr): partial_envs = _expand_envstr(expr) - return [{_base_factor_name(factor) for factor in e.split('-')} - for e in partial_envs] + return [{_base_factor_name(factor) for factor in e.split("-")} for e in partial_envs] def _expand_envstr(envstr): # split by commas not in groups - tokens = re.split(r'((?:\{[^}]+\})+)|,', envstr) - envlist = [''.join(g).strip() - for k, g in itertools.groupby(tokens, key=bool) if k] + tokens = re.split(r"((?:\{[^}]+\})+)|,", envstr) + envlist = ["".join(g).strip() for k, g in itertools.groupby(tokens, key=bool) if k] def expand(env): - tokens = re.split(r'\{([^}]+)\}', env) - parts = [re.sub('\s+', '', token).split(',') for token in tokens] - return [''.join(variant) for variant in itertools.product(*parts)] + tokens = re.split(r"\{([^}]+)\}", env) + parts = [re.sub("\s+", "", token).split(",") for token in tokens] + return ["".join(variant) for variant in itertools.product(*parts)] return mapcat(expand, envlist) @@ -931,6 +1074,7 @@ def mapcat(f, seq): class DepConfig: + def __init__(self, name, indexserver=None): self.name = name self.indexserver = indexserver @@ -939,13 +1083,14 @@ def __str__(self): if self.indexserver: if self.indexserver.name == "default": return self.name - return ":%s:%s" % (self.indexserver.name, self.name) + return ":{}:{}".format(self.indexserver.name, self.name) return str(self.name) __repr__ = __str__ class IndexServerConfig: + def __init__(self, name, url=None): self.name = name self.url = url @@ -959,12 +1104,12 @@ def __init__(self, name, url=None): class SectionReader: - def __init__(self, section_name, cfgparser, fallbacksections=None, - factors=(), prefix=None): + + 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.section_name = "{}:{}".format(prefix, section_name) self._cfg = cfgparser self.fallbacksections = fallbacksections or [] self.factors = factors @@ -985,7 +1130,7 @@ def addsubstitutions(self, _posargs=None, **kw): def getpath(self, name, defaultpath, replace=True): path = self.getstring(name, defaultpath, replace=replace) if path is not None: - toxinidir = self._subs['toxinidir'] + toxinidir = self._subs["toxinidir"] return toxinidir.join(path, abs=True) def getlist(self, name, sep="\n"): @@ -1011,7 +1156,7 @@ def _getdict(self, value, default, sep, replace=True): d = {} for line in value.split(sep): if line.strip(): - name, rest = line.split('=', 1) + name, rest = line.split("=", 1) d[name.strip()] = rest.strip() return d @@ -1021,8 +1166,7 @@ def getbool(self, name, default=None, replace=True): if not s or not replace: s = default if s is None: - raise KeyError("no config value [%s] %s found" % ( - self.section_name, name)) + raise KeyError("no config value [{}] {} found".format(self.section_name, name)) if not isinstance(s, bool): if s.lower() == "true": @@ -1030,8 +1174,7 @@ def getbool(self, name, default=None, replace=True): elif s.lower() == "false": s = False else: - raise tox.exception.ConfigError( - "boolean value %r needs to be 'True' or 'False'") + raise tox.exception.ConfigError("boolean value %r needs to be 'True' or 'False'") return s def getargvlist(self, name, default="", replace=True): @@ -1061,22 +1204,24 @@ def getstring(self, name, default=None, replace=True, crossonly=False): return x def _apply_factors(self, s): + def factor_line(line): - m = re.search(r'^([\w{}\.!,-]+)\:\s+(.+)', line) + m = re.search(r"^([\w{}\.!,-]+)\:\s+(.+)", line) if not m: return line expr, line = m.groups() - if any(included <= self.factors and not - any(x in self.factors for x in excluded) - for included, excluded in _split_factor_expr(expr)): + if any( + included <= self.factors and not any(x in self.factors for x in excluded) + for included, excluded in _split_factor_expr(expr) + ): return line lines = s.strip().splitlines() - return '\n'.join(filter(None, map(factor_line, lines))) + return "\n".join(filter(None, map(factor_line, lines))) def _replace(self, value, name=None, section_name=None, crossonly=False): - if '{' not in value: + if "{" not in value: return value section_name = section_name if section_name else self.section_name @@ -1087,35 +1232,38 @@ def _replace(self, value, name=None, section_name=None, crossonly=False): except tox.exception.MissingSubstitution: if not section_name.startswith(testenvprefix): raise tox.exception.ConfigError( - "substitution env:%r: unknown or recursive definition in " - "section %r." % (value, section_name)) + "substitution env:{!r}: unknown or recursive definition in" + " section {!r}.".format(value, section_name) + ) raise return replaced def _replace_if_needed(self, x, name, replace, crossonly): - if replace and x and hasattr(x, 'replace'): + if replace and x and hasattr(x, "replace"): x = self._replace(x, name=name, crossonly=crossonly) return x class Replacer: RE_ITEM_REF = re.compile( - r''' + r""" (?[^[:{}]+):)? # optional sub_type for special rules (?P(?:\[[^,{}]*\])?[^:,{}]*) # substitution key (?::(?P[^{}]*))? # default value [}] - ''', re.VERBOSE) + """, + re.VERBOSE, + ) def __init__(self, reader, crossonly=False): self.reader = reader self.crossonly = crossonly def do_replace(self, value): - ''' + """ Recursively expand substitutions starting from the innermost expression - ''' + """ def substitute_once(x): return self.RE_ITEM_REF.sub(self._replace_match, x) @@ -1130,7 +1278,7 @@ def substitute_once(x): def _replace_match(self, match): g = match.groupdict() - sub_value = g['substitution_value'] + sub_value = g["substitution_value"] if self.crossonly: if sub_value.startswith("["): return self._substitute_from_other_section(sub_value) @@ -1145,27 +1293,29 @@ def _replace_match(self, match): # special case: opts and packages. Leave {opts} and # {packages} intact, they are replaced manually in # _venv.VirtualEnv.run_install_command. - if sub_value in ('opts', 'packages'): - return '{%s}' % sub_value + if sub_value in ("opts", "packages"): + return "{{{}}}".format(sub_value) try: - sub_type = g['sub_type'] + sub_type = g["sub_type"] except KeyError: raise tox.exception.ConfigError( - "Malformed substitution; no substitution type provided") + "Malformed substitution; no substitution type provided" + ) if sub_type == "env": return self._replace_env(match) if sub_type is not None: raise tox.exception.ConfigError( - "No support for the %s substitution type" % sub_type) + "No support for the {} substitution type".format(sub_type) + ) return self._replace_substitution(match) def _replace_env(self, match): - envkey = match.group('substitution_value') + envkey = match.group("substitution_value") if not envkey: - raise tox.exception.ConfigError('env: requires an environment variable name') - default = match.group('default_value') + raise tox.exception.ConfigError("env: requires an environment variable name") + default = match.group("default_value") envvalue = self.reader.get_environ_value(envkey) if envvalue is not None: return envvalue @@ -1180,17 +1330,18 @@ def _substitute_from_other_section(self, key): cfg = self.reader._cfg if section in cfg and item in cfg[section]: if (section, item) in self.reader._subststack: - raise ValueError('%s already in %s' % ( - (section, item), self.reader._subststack)) + raise ValueError( + "{} already in {}".format((section, item), self.reader._subststack) + ) x = str(cfg[section][item]) - return self.reader._replace(x, name=item, section_name=section, - crossonly=self.crossonly) + return self.reader._replace( + x, name=item, section_name=section, crossonly=self.crossonly + ) - raise tox.exception.ConfigError( - "substitution key %r not found" % key) + raise tox.exception.ConfigError("substitution key {!r} not found".format(key)) def _replace_substitution(self, match): - sub_key = match.group('substitution_value') + sub_key = match.group("substitution_value") val = self.reader._subs.get(sub_key, None) if val is None: val = self._substitute_from_other_section(sub_key) @@ -1200,6 +1351,7 @@ def _replace_substitution(self, match): class _ArgvlistReader: + @classmethod def getargvlist(cls, reader, value, replace=True): """Parse ``commands`` argvlist multiline string. @@ -1231,8 +1383,10 @@ def getargvlist(cls, reader, value, replace=True): else: if current_command: raise tox.exception.ConfigError( - "line-continuation ends nowhere while resolving for [%s] %s" % - (reader.section_name, "commands")) + "line-continuation ends nowhere while resolving for [{}] {}".format( + reader.section_name, "commands" + ) + ) return commands @classmethod @@ -1259,7 +1413,7 @@ def processcommand(cls, reader, command, replace=True): new_arg = "" new_word = reader._replace(word) new_word = reader._replace(new_word) - new_word = new_word.replace('\\{', '{').replace('\\}', '}') + new_word = new_word.replace("\\{", "{").replace("\\}", "}") new_arg += new_word newcommand += new_arg else: @@ -1269,14 +1423,16 @@ def processcommand(cls, reader, command, replace=True): # use all values as is in argv. shlexer = shlex.shlex(newcommand, posix=True) shlexer.whitespace_split = True - shlexer.escape = '' + shlexer.escape = "" return list(shlexer) class CommandParser(object): + class State(object): + def __init__(self): - self.word = '' + self.word = "" self.depth = 0 self.yield_words = [] @@ -1287,16 +1443,20 @@ def words(self): ps = CommandParser.State() def word_has_ended(): - return ((cur_char in string.whitespace and ps.word and - ps.word[-1] not in string.whitespace) or - (cur_char == '{' and ps.depth == 0 and not ps.word.endswith('\\')) or - (ps.depth == 0 and ps.word and ps.word[-1] == '}') or - (cur_char not in string.whitespace and ps.word and - ps.word.strip() == '')) + return ( + ( + cur_char in string.whitespace + and ps.word + and ps.word[-1] not in string.whitespace + ) + or (cur_char == "{" and ps.depth == 0 and not ps.word.endswith("\\")) + or (ps.depth == 0 and ps.word and ps.word[-1] == "}") + or (cur_char not in string.whitespace and ps.word and ps.word.strip() == "") + ) def yield_this_word(): yieldword = ps.word - ps.word = '' + ps.word = "" if yieldword: ps.yield_words.append(yieldword) @@ -1318,11 +1478,11 @@ def pop_substitution(): if ps.depth == 0: yield_if_word_ended() accumulate() - elif cur_char == '{': + elif cur_char == "{": yield_if_word_ended() accumulate() push_substitution() - elif cur_char == '}': + elif cur_char == "}": accumulate() pop_substitution() else: @@ -1335,6 +1495,6 @@ def pop_substitution(): def getcontextname(): - if any(env in os.environ for env in ['JENKINS_URL', 'HUDSON_URL']): - return 'jenkins' + if any(env in os.environ for env in ["JENKINS_URL", "HUDSON_URL"]): + return "jenkins" return None diff --git a/tox/constants.py b/tox/constants.py index 1eaf64304..049a08dc7 100644 --- a/tox/constants.py +++ b/tox/constants.py @@ -5,56 +5,60 @@ import sys as _sys -def _contruct_default_factors(version_tuples, other_interpreters): - default_factors = {'py': _sys.executable, 'py2': 'python2', 'py3': 'python3'} - default_factors.update({'py%s%s' % (major, minor): 'python%s.%s' % (major, minor) - for major, minor in version_tuples}) +def _construct_default_factors(version_tuples, other_interpreters): + default_factors = {"py": _sys.executable, "py2": "python2", "py3": "python3"} + default_factors.update( + { + "py{}{}".format(major, minor): "python{}.{}".format(major, minor) + for major, minor in version_tuples + } + ) default_factors.update({interpreter: interpreter for interpreter in other_interpreters}) return default_factors class PYTHON: CPYTHON_VERSION_TUPLES = [(2, 7), (3, 4), (3, 5), (3, 6), (3, 7)] - OTHER_PYTHON_INTERPRETERS = ['jython', 'pypy', 'pypy3'] - DEFAULT_FACTORS = _contruct_default_factors(CPYTHON_VERSION_TUPLES, OTHER_PYTHON_INTERPRETERS) - CURRENT_RELEASE_ENV = 'py36' + OTHER_PYTHON_INTERPRETERS = ["jython", "pypy", "pypy3"] + DEFAULT_FACTORS = _construct_default_factors(CPYTHON_VERSION_TUPLES, OTHER_PYTHON_INTERPRETERS) + CURRENT_RELEASE_ENV = "py36" """Should hold currently released py -> for easy updating""" - QUICKSTART_PY_ENVS = ['py27', 'py34', 'py35', CURRENT_RELEASE_ENV, 'pypy', 'jython'] + QUICKSTART_PY_ENVS = ["py27", "py34", "py35", CURRENT_RELEASE_ENV, "pypy", "jython"] """For choices in tox-quickstart""" class INFO: - DEFAULT_CONFIG_NAME = 'tox.ini' + DEFAULT_CONFIG_NAME = "tox.ini" IS_WIN = _sys.platform == "win32" class PIP: - SHORT_OPTIONS = ['c', 'e', 'r', 'b', 't', 'd'] + SHORT_OPTIONS = ["c", "e", "r", "b", "t", "d"] LONG_OPTIONS = [ - 'build', - 'cache-dir', - 'client-cert', - 'constraint', - 'download', - 'editable', - 'exists-action', - 'extra-index-url', - 'global-option', - 'find-links', - 'index-url', - 'install-options', - 'prefix', - 'proxy', - 'no-binary', - 'only-binary', - 'requirement', - 'retries', - 'root', - 'src', - 'target', - 'timeout', - 'trusted-host', - 'upgrade-strategy', + "build", + "cache-dir", + "client-cert", + "constraint", + "download", + "editable", + "exists-action", + "extra-index-url", + "global-option", + "find-links", + "index-url", + "install-options", + "prefix", + "proxy", + "no-binary", + "only-binary", + "requirement", + "retries", + "root", + "src", + "target", + "timeout", + "trusted-host", + "upgrade-strategy", ] - INSTALL_SHORT_OPTIONS_ARGUMENT = ['-%s' % option for option in SHORT_OPTIONS] - INSTALL_LONG_OPTIONS_ARGUMENT = ['--%s' % option for option in LONG_OPTIONS] + INSTALL_SHORT_OPTIONS_ARGUMENT = ["-{}".format(option) for option in SHORT_OPTIONS] + INSTALL_LONG_OPTIONS_ARGUMENT = ["--{}".format(option) for option in LONG_OPTIONS] diff --git a/tox/exception.py b/tox/exception.py index aefad5bfc..01d0968fe 100644 --- a/tox/exception.py +++ b/tox/exception.py @@ -14,28 +14,31 @@ def exit_code_str(exception_name, command, exit_code): Even a normal method failed with "TypeError: descriptor '__getattribute__' requires a 'BaseException' object but received a 'type'". """ - str_ = "%s for command %s" % (exception_name, command) + str_ = "{} for command {}".format(exception_name, command) if exit_code is not None: - str_ += " (exited with code %d)" % (exit_code) - if (os.name == 'posix') and (exit_code > 128): - signals = {number: name - for name, number in vars(signal).items() - if name.startswith("SIG")} + str_ += " (exited with code {:d})".format(exit_code) + if (os.name == "posix") and (exit_code > 128): + signals = { + number: name for name, number in vars(signal).items() if name.startswith("SIG") + } number = exit_code - 128 name = signals.get(number) if name: - str_ += ("\nNote: this might indicate a fatal error signal " - "(%d - 128 = %d: %s)" % (number+128, number, name)) + str_ += ( + "\nNote: this might indicate a fatal error signal " + "({:d} - 128 = {:d}: {})".format(number + 128, number, name) + ) return str_ class Error(Exception): + def __str__(self): - return "%s: %s" % (self.__class__.__name__, self.args[0]) + return "{}: {}".format(self.__class__.__name__, self.args[0]) class MissingSubstitution(Error): - FLAG = 'TOX_MISSING_SUBSTITUTION' + FLAG = "TOX_MISSING_SUBSTITUTION" """placeholder for debugging configurations""" def __init__(self, name): @@ -56,6 +59,7 @@ class InterpreterNotFound(Error): class InvocationError(Error): """An error while invoking a script.""" + def __init__(self, command, exit_code=None): super(Error, self).__init__(command, exit_code) self.command = command diff --git a/tox/interpreters.py b/tox/interpreters.py index 219678082..c13a50338 100644 --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -10,6 +10,7 @@ class Interpreters: + def __init__(self, hook): self.name2executable = {} self.executable2info = {} @@ -45,11 +46,15 @@ def get_sitepackagesdir(self, info, envdir): return "" envdir = str(envdir) try: - res = exec_on_interpreter(info.executable, - [inspect.getsource(sitepackagesdir), - "print(sitepackagesdir(%r))" % envdir]) + res = exec_on_interpreter( + info.executable, + [ + inspect.getsource(sitepackagesdir), + "print(sitepackagesdir({!r}))".format(envdir), + ], + ) except ExecFailed as e: - print("execution failed: %s -- %s" % (e.out, e.err)) + print("execution failed: {} -- {}".format(e.out, e.err)) return "" else: return res["dir"] @@ -58,11 +63,9 @@ def get_sitepackagesdir(self, info, envdir): def run_and_get_interpreter_info(name, executable): assert executable try: - result = exec_on_interpreter(executable, [inspect.getsource(pyinfo), - "print(pyinfo())"]) + result = exec_on_interpreter(executable, [inspect.getsource(pyinfo), "print(pyinfo())"]) except ExecFailed as e: - return NoInterpreterInfo(name, executable=e.executable, - out=e.out, err=e.err) + return NoInterpreterInfo(name, executable=e.executable, out=e.out, err=e.err) else: return InterpreterInfo(name, executable, **result) @@ -71,6 +74,7 @@ def exec_on_interpreter(executable, source): if isinstance(source, list): source = "\n".join(source) from subprocess import Popen, PIPE + args = [str(executable)] popen = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) popen.stdin.write(source.encode("utf8")) @@ -80,12 +84,12 @@ def exec_on_interpreter(executable, source): try: result = eval(out.strip()) except Exception: - raise ExecFailed(executable, source, out, - "could not decode %r" % out) + raise ExecFailed(executable, source, out, "could not decode {!r}".format(out)) return result class ExecFailed(Exception): + def __init__(self, executable, source, out, err): self.executable = executable self.source = source @@ -104,15 +108,13 @@ def __init__(self, name, executable, version_info, sysplatform): self.sysplatform = sysplatform def __str__(self): - return "" % ( - self.executable, self.version_info) + return "".format(self.executable, self.version_info) class NoInterpreterInfo: runnable = False - def __init__(self, name, executable=None, - out=None, err="not found"): + def __init__(self, name, executable=None, out=None, err="not found"): self.name = name self.executable = executable self.version_info = None @@ -121,17 +123,20 @@ def __init__(self, name, executable=None, def __str__(self): if self.executable: - return "" % self.executable + return "".format(self.executable) else: - return "" % self.name + return "".format(self.name) if not tox.INFO.IS_WIN: + @tox.hookimpl def tox_get_python_executable(envconfig): return py.path.local.sysfind(envconfig.basepython) + else: + @tox.hookimpl def tox_get_python_executable(envconfig): name = envconfig.basepython @@ -143,7 +148,7 @@ def tox_get_python_executable(envconfig): m = re.match(r"python(\d)\.(\d)", name) if m: # The standard names are in predictable places. - actual = r"c:\python%s%s\python.exe" % m.groups() + actual = r"c:\python{}{}\python.exe".format(*m.groups()) if not actual: actual = win32map.get(name, None) if actual: @@ -156,31 +161,28 @@ def tox_get_python_executable(envconfig): return locate_via_py(*m.groups()) # Exceptions to the usual windows mapping - win32map = { - 'python': sys.executable, - 'jython': r"c:\jython2.5.1\jython.bat", - } + win32map = {"python": sys.executable, "jython": r"c:\jython2.5.1\jython.bat"} def locate_via_py(v_maj, v_min): - ver = "-%s.%s" % (v_maj, v_min) + ver = "-{}.{}".format(v_maj, v_min) script = "import sys; print(sys.executable)" - py_exe = distutils.spawn.find_executable('py') + py_exe = distutils.spawn.find_executable("py") if py_exe: proc = subprocess.Popen( - (py_exe, ver, '-c', script), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + (py_exe, ver, "-c", script), stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) out, _ = proc.communicate() if not proc.returncode: - return out.decode('UTF-8').strip() + return out.decode("UTF-8").strip() def pyinfo(): import sys - return {"version_info": tuple(sys.version_info), - "sysplatform": sys.platform} + + return {"version_info": tuple(sys.version_info), "sysplatform": sys.platform} def sitepackagesdir(envdir): import distutils.sysconfig + return {"dir": distutils.sysconfig.get_python_lib(prefix=envdir)} diff --git a/tox/result.py b/tox/result.py index e84e85dc2..5d23f4815 100644 --- a/tox/result.py +++ b/tox/result.py @@ -8,6 +8,7 @@ class ResultLog: + def __init__(self, data=None): if not data: self.dict = {} @@ -39,6 +40,7 @@ def dumps_json(self): class EnvLog: + def __init__(self, reportlog, name, dict): self.reportlog = reportlog self.name = name @@ -46,19 +48,19 @@ def __init__(self, reportlog, name, dict): def set_python_info(self, pythonexecutable): pythonexecutable = py.path.local(pythonexecutable) - out = pythonexecutable.sysexec("-c", - "import sys; " - "print(sys.executable);" - "print(list(sys.version_info)); " - "print(sys.version)") + out = pythonexecutable.sysexec( + "-c", + "import sys; " + "print(sys.executable);" + "print(list(sys.version_info)); " + "print(sys.version)", + ) lines = out.splitlines() executable = lines.pop(0) version_info = eval(lines.pop(0)) version = "\n".join(lines) self.dict["python"] = { - "executable": executable, - "version_info": version_info, - "version": version, + "executable": executable, "version_info": version_info, "version": version } def get_commandlog(self, name): @@ -69,6 +71,7 @@ def set_installed(self, packages): class CommandLog: + def __init__(self, envlog, list): self.envlog = envlog self.list = list diff --git a/tox/session.py b/tox/session.py index 043c0e5d3..e74b81f92 100644 --- a/tox/session.py +++ b/tox/session.py @@ -59,24 +59,29 @@ def show_help(config): tw.write(config._parser._format_help()) tw.line() tw.line("Environment variables", bold=True) - tw.line("TOXENV: comma separated list of environments " - "(overridable by '-e')") - tw.line("TOX_TESTENV_PASSENV: space-separated list of extra " - "environment variables to be passed into test command " - "environments") + tw.line("TOXENV: comma separated list of environments (overridable by '-e')") + tw.line( + "TOX_TESTENV_PASSENV: space-separated list of extra environment variables to be " + "passed into test command environments" + ) def show_help_ini(config): tw = py.io.TerminalWriter() tw.sep("-", "per-testenv attributes") for env_attr in config._testenv_attr: - tw.line("%-15s %-8s default: %s" % - (env_attr.name, "<" + env_attr.type + ">", env_attr.default), bold=True) + tw.line( + "{:<15} {:<8} default: {}".format( + env_attr.name, "<" + env_attr.type + ">", env_attr.default + ), + bold=True, + ) tw.line(env_attr.help) tw.line() class Action(object): + def __init__(self, session, venv, msg, args): self.venv = venv self.msg = msg @@ -106,12 +111,12 @@ def __exit__(self, *args): def setactivity(self, name, msg): self.activity = name if msg: - self.report.verbosity0("%s %s: %s" % (self.venvname, name, msg), bold=True) + self.report.verbosity0("{} {}: {}".format(self.venvname, name, msg), bold=True) else: - self.report.verbosity1("%s %s: %s" % (self.venvname, name, msg), bold=True) + self.report.verbosity1("{} {}: {}".format(self.venvname, name, msg), bold=True) def info(self, name, msg): - self.report.verbosity1("%s %s: %s" % (self.venvname, name, msg), bold=True) + self.report.verbosity1("{} {}: {}".format(self.venvname, name, msg), bold=True) def _initlogpath(self, actionid): if self.venv: @@ -119,12 +124,12 @@ def _initlogpath(self, actionid): else: logdir = self.session.config.logdir try: - log_count = len(logdir.listdir("%s-*" % actionid)) + log_count = len(logdir.listdir("{}-*".format(actionid))) except (py.error.ENOENT, py.error.ENOTDIR): logdir.ensure(dir=1) log_count = 0 - path = logdir.join("%s-%s.log" % (actionid, log_count)) - f = path.open('w') + path = logdir.join("{}-{}.log".format(actionid, log_count)) + f = path.open("w") f.flush() return f @@ -133,10 +138,10 @@ def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore resultjson = self.session.config.option.resultjson if resultjson or redirect: fout = self._initlogpath(self.id) - fout.write("actionid: %s\nmsg: %s\ncmdargs: %r\n\n" % (self.id, self.msg, args)) + fout.write("actionid: {}\nmsg: {}\ncmdargs: {!r}\n\n".format(self.id, self.msg, args)) fout.flush() outpath = py.path.local(fout.name) - fin = outpath.open('rb') + fin = outpath.open("rb") fin.read() # read the header, so it won't be written to stdout stdout = fout elif returnout: @@ -145,11 +150,11 @@ def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore # FIXME XXX cwd = self.session.config.cwd cwd = py.path.local() try: - popen = self._popen(args, cwd, env=env, - stdout=stdout, stderr=subprocess.STDOUT) + popen = self._popen(args, cwd, env=env, stdout=stdout, stderr=subprocess.STDOUT) except OSError as e: - self.report.error("invocation failed (errno %d), args: %s, cwd: %s" % - (e.errno, args, cwd)) + self.report.error( + "invocation failed (errno {:d}), args: {}, cwd: {}".format(e.errno, args, cwd) + ) raise popen.outpath = outpath popen.args = [str(x) for x in args] @@ -165,7 +170,7 @@ def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore raise ValueError("stderr must not be piped here") # we read binary from the process and must write using a # binary stream - buf = getattr(sys.stdout, 'buffer', sys.stdout) + buf = getattr(sys.stdout, "buffer", sys.stdout) out = None last_time = time.time() while 1: @@ -174,7 +179,7 @@ def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore data = fin.read(1) if data: buf.write(data) - if b'\n' in data or (time.time() - last_time) > 1: + if b"\n" in data or (time.time() - last_time) > 1: # we flush on newlines or after 1 second to # provide quick enough feedback to the user # when printing a dot per test @@ -201,16 +206,16 @@ def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore if ret and not ignore_ret: invoked = " ".join(map(str, popen.args)) if outpath: - self.report.error("invocation failed (exit code %d), logfile: %s" % - (ret, outpath)) + self.report.error( + "invocation failed (exit code {:d}), logfile: {}".format(ret, outpath) + ) out = outpath.read() self.report.error(out) if hasattr(self, "commandlog"): self.commandlog.add_command(popen.args, out, ret) - raise tox.exception.InvocationError( - "%s (see %s)" % (invoked, outpath), ret) + raise tox.exception.InvocationError("{} (see {})".format(invoked, outpath), ret) else: - raise tox.exception.InvocationError("%r" % (invoked,), ret) + raise tox.exception.InvocationError("{!r}".format(invoked), ret) if not out and outpath: out = outpath.read() if hasattr(self, "commandlog"): @@ -226,15 +231,22 @@ def _rewriteargs(self, cwd, args): # subprocess does not always take kindly to .py scripts so adding the interpreter here if tox.INFO.IS_WIN: ext = os.path.splitext(str(newargs[0]))[1].lower() - if ext == '.py' and self.venv: + if ext == ".py" and self.venv: newargs = [str(self.venv.envconfig.envpython)] + newargs return newargs def _popen(self, args, cwd, stdout, stderr, env=None): if env is None: env = os.environ.copy() - return self.session.popen(self._rewriteargs(cwd, args), shell=False, cwd=str(cwd), - universal_newlines=True, stdout=stdout, stderr=stderr, env=env) + return self.session.popen( + self._rewriteargs(cwd, args), + shell=False, + cwd=str(cwd), + universal_newlines=True, + stdout=stdout, + stderr=stderr, + env=env, + ) class Verbosity(object): @@ -256,8 +268,9 @@ def __init__(self, session): @property def verbosity(self): if self.session: - return (self.session.config.option.verbose_level - - self.session.config.option.quiet_level) + return ( + self.session.config.option.verbose_level - self.session.config.option.quiet_level + ) else: return Verbosity.DEBUG @@ -265,21 +278,23 @@ def logpopen(self, popen, env): """ log information about the action.popen() created process. """ cmd = " ".join(map(str, popen.args)) if popen.outpath: - self.verbosity1(" %s$ %s >%s" % (popen.cwd, cmd, popen.outpath)) + self.verbosity1(" {}$ {} >{}".format(popen.cwd, cmd, popen.outpath)) else: - self.verbosity1(" %s$ %s " % (popen.cwd, cmd)) + self.verbosity1(" {}$ {} ".format(popen.cwd, cmd)) def logaction_start(self, action): - msg = action.msg + " " + " ".join(map(str, action.args)) - self.verbosity2("%s start: %s" % (action.venvname, msg), bold=True) + msg = "{} {}".format(action.msg, " ".join(map(str, action.args))) + self.verbosity2("{} start: {}".format(action.venvname, msg), bold=True) assert not hasattr(action, "_starttime") action._starttime = time.time() def logaction_finish(self, action): duration = time.time() - action._starttime - self.verbosity2("%s finish: %s after %.2f seconds" % ( - action.venvname, action.msg, duration), bold=True) - delattr(action, '_starttime') + self.verbosity2( + "{} finish: {} after {:.2f} seconds".format(action.venvname, action.msg, duration), + bold=True, + ) + delattr(action, "_starttime") def startsummary(self): if self.verbosity >= Verbosity.QUIET: @@ -291,7 +306,7 @@ def info(self, msg): def using(self, msg): if self.verbosity >= 1: - self.logline("using %s" % (msg,), bold=True) + self.logline("using {}".format(msg), bold=True) def keyboard_interrupt(self): self.error("KEYBOARDINTERRUPT") @@ -312,31 +327,31 @@ def good(self, msg): def warning(self, msg): if self.verbosity >= Verbosity.QUIET: - self.logline("WARNING:" + msg, red=True) + self.logline("WARNING: {}".format(msg), red=True) def error(self, msg): if self.verbosity >= Verbosity.QUIET: - self.logline("ERROR: " + msg, red=True) + self.logline("ERROR: {}".format(msg), red=True) def skip(self, msg): if self.verbosity >= Verbosity.QUIET: - self.logline("SKIPPED:" + msg, yellow=True) + self.logline("SKIPPED: {}".format(msg), yellow=True) def logline(self, msg, **opts): self._reportedlines.append(msg) - self.tw.line("%s" % msg, **opts) + self.tw.line("{}".format(msg), **opts) def verbosity0(self, msg, **opts): if self.verbosity >= Verbosity.DEFAULT: - self.logline("%s" % msg, **opts) + self.logline("{}".format(msg), **opts) def verbosity1(self, msg, **opts): if self.verbosity >= Verbosity.INFO: - self.logline("%s" % msg, **opts) + self.logline("{}".format(msg), **opts) def verbosity2(self, msg, **opts): if self.verbosity >= Verbosity.DEBUG: - self.logline("%s" % msg, **opts) + self.logline("{}".format(msg), **opts) # def log(self, msg): # print(msg, file=sys.stderr) @@ -352,14 +367,11 @@ def __init__(self, config, popen=subprocess.Popen, Report=Reporter): self.report = Report(self) self.make_emptydir(config.logdir) config.logdir.ensure(dir=1) - self.report.using("tox.ini: %s" % (self.config.toxinipath,)) + self.report.using("tox.ini: {}".format(self.config.toxinipath)) self._spec2pkg = {} self._name2venv = {} try: - self.venvlist = [ - self.getvenv(x) - for x in self.config.envlist - ] + self.venvlist = [self.getvenv(x) for x in self.config.envlist] except LookupError: raise SystemExit(1) except tox.exception.ConfigError as e: @@ -374,12 +386,13 @@ def hook(self): def _makevenv(self, name): envconfig = self.config.envconfigs.get(name, None) if envconfig is None: - self.report.error("unknown environment %r" % name) + self.report.error("unknown environment {!r}".format(name)) raise LookupError(name) elif envconfig.envdir == self.config.toxinidir: self.report.error( - "venv %r in %s would delete project" % (name, envconfig.envdir)) - raise tox.exception.ConfigError('envdir must not equal toxinidir') + "venv {!r} in {} would delete project".format(name, envconfig.envdir) + ) + raise tox.exception.ConfigError("envdir must not equal toxinidir") venv = VirtualEnv(envconfig=envconfig, session=self) self._name2venv[name] = venv return venv @@ -397,7 +410,7 @@ def newaction(self, venv, msg, *args): return action def runcommand(self): - self.report.using("tox-%s from %s" % (tox.__version__, tox.__file__)) + self.report.using("tox-{} from {}".format(tox.__version__, tox.__file__)) verbosity = (self.report.verbosity > Verbosity.DEFAULT) if self.config.option.showconfig: self.showconfig() @@ -412,7 +425,7 @@ def _copyfiles(self, srcdir, pathlist, destdir): for relpath in pathlist: src = srcdir.join(relpath) if not src.check(): - self.report.error("missing source file: %s" % (src,)) + self.report.error("missing source file: {}".format(src)) raise SystemExit(1) target = destdir.join(relpath) target.dirpath().ensure(dir=1) @@ -423,22 +436,30 @@ def _makesdist(self): if not setup.check(): self.report.error( "No setup.py file found. The expected location is:\n" - " %s\n" + " {}\n" "You can\n" " 1. Create one:\n" " https://packaging.python.org/tutorials/distributing-packages/#setup-py\n" " 2. Configure tox to avoid running sdist:\n" " http://tox.readthedocs.io/en/latest/example/general.html" - "#avoiding-expensive-sdist" % setup + "#avoiding-expensive-sdist".format(setup) ) raise SystemExit(1) action = self.newaction(None, "packaging") with action: action.setactivity("sdist-make", setup) self.make_emptydir(self.config.distdir) - action.popen([sys.executable, setup, "sdist", "--formats=zip", - "--dist-dir", self.config.distdir, ], - cwd=self.config.setupdir) + action.popen( + [ + sys.executable, + setup, + "sdist", + "--formats=zip", + "--dist-dir", + self.config.distdir, + ], + cwd=self.config.setupdir, + ) try: return self.config.distdir.listdir()[0] except py.error.ENOENT: @@ -446,32 +467,32 @@ def _makesdist(self): data = [] with open(str(setup)) as fp: for line in fp: - if line and line[0] == '#': + if line and line[0] == "#": continue data.append(line) - if not ''.join(data).strip(): - self.report.error( - 'setup.py is empty' - ) + if not "".join(data).strip(): + self.report.error("setup.py is empty") raise SystemExit(1) self.report.error( - 'No dist directory found. Please check setup.py, e.g with:\n' - ' python setup.py sdist' + "No dist directory found. Please check setup.py, e.g with:\n" + " python setup.py sdist" ) raise SystemExit(1) def make_emptydir(self, path): if path.check(): - self.report.info(" removing %s" % path) + self.report.info(" removing {}".format(path)) shutil.rmtree(str(path), ignore_errors=True) path.ensure(dir=1) def setupenv(self, venv): if venv.envconfig.missing_subs: venv.status = ( - "unresolvable substitution(s): %s. " - "Environment variables are missing or defined recursively." % - (','.join(["'%s'" % m for m in venv.envconfig.missing_subs]))) + "unresolvable substitution(s): {}. " + "Environment variables are missing or defined recursively.".format( + ",".join(["'{}'".format(m) for m in venv.envconfig.missing_subs]) + ) + ) return if not venv.matching_platform(): venv.status = "platform mismatch" @@ -487,25 +508,28 @@ def setupenv(self, venv): if e.args[0] != 2: raise status = ( - "Error creating virtualenv. Note that spaces in paths are " - "not supported by virtualenv. Error details: %r" % e) + "Error creating virtualenv. Note that spaces in paths are " + "not supported by virtualenv. Error details: {!r}".format(e) + ) except tox.exception.InvocationError as e: status = ( - "Error creating virtualenv. Note that some special " - "characters (e.g. ':' and unicode symbols) in paths are " - "not supported by virtualenv. Error details: %r" % e) + "Error creating virtualenv. Note that some special characters (e.g. ':' and " + "unicode symbols) in paths are not supported by virtualenv. Error details: " + "{!r}".format(e) + ) except tox.exception.InterpreterNotFound as e: status = e if self.config.option.skip_missing_interpreters: default_ret_code = 0 if status: + str_status = str(status) commandlog = envlog.get_commandlog("setup") - commandlog.add_command(["setup virtualenv"], str(status), default_ret_code) + commandlog.add_command(["setup virtualenv"], str_status, default_ret_code) venv.status = status if default_ret_code == 0: - self.report.skip(str(status)) + self.report.skip(str_status) else: - self.report.error(str(status)) + self.report.error(str_status) return False commandpath = venv.getcommandpath("python") envlog.set_python_info(commandpath) @@ -550,31 +574,31 @@ def get_installpkg_path(self): :return: Path to the distribution :rtype: py.path.local """ - if not self.config.option.sdistonly and (self.config.sdistsrc or - self.config.option.installpkg): + if ( + not self.config.option.sdistonly + and (self.config.sdistsrc or self.config.option.installpkg) + ): path = self.config.option.installpkg if not path: path = self.config.sdistsrc path = self._resolve_pkg(path) - self.report.info("using package %r, skipping 'sdist' activity " % - str(path)) + self.report.info("using package {!r}, skipping 'sdist' activity ".format(str(path))) else: try: path = self._makesdist() except tox.exception.InvocationError: v = sys.exc_info()[1] - self.report.error("FAIL could not package project - v = %r" % - v) + self.report.error("FAIL could not package project - v = {!r}".format(v)) return sdistfile = self.config.distshare.join(path.basename) if sdistfile != path: - self.report.info("copying new sdistfile to %r" % - str(sdistfile)) + self.report.info("copying new sdistfile to {!r}".format(str(sdistfile))) try: sdistfile.dirpath().ensure(dir=1) except py.error.Error: - self.report.warning("could not copy distfile to %s" % - sdistfile.dirpath()) + self.report.warning( + "could not copy distfile to {}".format(sdistfile.dirpath()) + ) else: path.copy(sdistfile) return path @@ -634,26 +658,26 @@ def _summary(self): for venv in self.venvlist: status = venv.status if isinstance(status, tox.exception.InterpreterNotFound): - msg = " %s: %s" % (venv.envconfig.envname, str(status)) + msg = " {}: {}".format(venv.envconfig.envname, str(status)) if self.config.option.skip_missing_interpreters: self.report.skip(msg) else: retcode = 1 self.report.error(msg) elif status == "platform mismatch": - msg = " %s: %s" % (venv.envconfig.envname, str(status)) + msg = " {}: {}".format(venv.envconfig.envname, str(status)) self.report.skip(msg) elif status and status == "ignored failed command": - msg = " %s: %s" % (venv.envconfig.envname, str(status)) + msg = " {}: {}".format(venv.envconfig.envname, str(status)) self.report.good(msg) elif status and status != "skipped tests": - msg = " %s: %s" % (venv.envconfig.envname, str(status)) + msg = " {}: {}".format(venv.envconfig.envname, str(status)) self.report.error(msg) retcode = 1 else: if not status: status = "commands succeeded" - self.report.good(" %s: %s" % (venv.envconfig.envname, status)) + self.report.good(" {}: {}".format(venv.envconfig.envname, status)) if not retcode: self.report.good(" congratulations :)") @@ -661,7 +685,7 @@ def _summary(self): if path: path = py.path.local(path) path.write(self.resultlog.dumps_json()) - self.report.line("wrote json report at: %s" % path) + self.report.line("wrote json report at: {}".format(path)) return retcode def showconfig(self): @@ -675,23 +699,22 @@ def showconfig(self): self.report.keyvalue("skipsdist: ", self.config.skipsdist) self.report.tw.line() for envconfig in self.config.envconfigs.values(): - self.report.line("[testenv:%s]" % envconfig.envname, bold=True) + self.report.line("[testenv:{}]".format(envconfig.envname), bold=True) for attr in self.config._parser._testenv_attr: - self.report.line(" %-15s = %s" - % (attr.name, getattr(envconfig, attr.name))) + self.report.line(" {:<15} = {}".format(attr.name, getattr(envconfig, attr.name))) def showenvs(self, all_envs=False, description=False): env_conf = self.config.envconfigs # this contains all environments default = self.config.envlist # this only the defaults extra = sorted(e for e in env_conf if e not in default) if all_envs else [] if description: - self.report.line('default environments:') + self.report.line("default environments:") max_length = max(len(env) for env in (default + extra)) def report_env(e): if description: - text = env_conf[e].description or '[no description]' - msg = '{} -> {}'.format(e.ljust(max_length), text).strip() + text = env_conf[e].description or "[no description]" + msg = "{} -> {}".format(e.ljust(max_length), text).strip() else: msg = e self.report.line(msg) @@ -700,19 +723,18 @@ def report_env(e): report_env(e) if all_envs and extra: if description: - self.report.line('') - self.report.line('additional environments:') + self.report.line("") + self.report.line("additional environments:") for e in extra: report_env(e) def info_versions(self): - versions = ['tox-%s' % tox.__version__] + versions = ["tox-{}".format(tox.__version__)] proc = subprocess.Popen( - (sys.executable, '-m', 'virtualenv', '--version'), - stdout=subprocess.PIPE, + (sys.executable, "-m", "virtualenv", "--version"), stdout=subprocess.PIPE ) out, _ = proc.communicate() - versions.append('virtualenv-{}'.format(out.decode('UTF-8').strip())) + versions.append("virtualenv-{}".format(out.decode("UTF-8").strip())) self.report.keyvalue("tool-versions:", " ".join(versions)) def _resolve_pkg(self, pkgspec): @@ -730,7 +752,7 @@ def _resolvepkg(self, pkgspec): return p if not p.dirpath().check(dir=1): raise tox.exception.MissingDirectory(p.dirpath()) - self.report.info("determining %s" % p) + self.report.info("determining {}".format(p)) candidates = p.dirpath().listdir(p.basename) if len(candidates) == 0: raise tox.exception.MissingDependency(pkgspec) @@ -741,8 +763,7 @@ def _resolvepkg(self, pkgspec): if ver is not None: items.append((ver, x)) else: - self.report.warning("could not determine version of: %s" % - str(x)) + self.report.warning("could not determine version of: {}".format(str(x))) items.sort() if not items: raise tox.exception.MissingDependency(pkgspec) diff --git a/tox/venv.py b/tox/venv.py index 1f05c3a1a..35b5d4a1d 100755 --- a/tox/venv.py +++ b/tox/venv.py @@ -11,8 +11,8 @@ class CreationConfig: - def __init__(self, md5, python, version, sitepackages, - usedevelop, deps, alwayscopy): + + def __init__(self, md5, python, version, sitepackages, usedevelop, deps, alwayscopy): self.md5 = md5 self.python = python self.version = version @@ -22,11 +22,14 @@ def __init__(self, md5, python, version, sitepackages, self.deps = deps def writeconfig(self, path): - lines = ["%s %s" % (self.md5, self.python)] - lines.append("%s %d %d %d" % (self.version, self.sitepackages, - self.usedevelop, self.alwayscopy)) + lines = [ + "{} {}".format(self.md5, self.python), + "{} {:d} {:d} {:d}".format( + self.version, self.sitepackages, self.usedevelop, self.alwayscopy + ), + ] for dep in self.deps: - lines.append("%s %s" % dep) + lines.append("{} {}".format(*dep)) path.ensure() path.write("\n".join(lines)) @@ -49,16 +52,20 @@ def readconfig(cls, path): return None def matches(self, other): - return (other and self.md5 == other.md5 and - self.python == other.python and - self.version == other.version and - self.sitepackages == other.sitepackages and - self.usedevelop == other.usedevelop and - self.alwayscopy == other.alwayscopy and - self.deps == other.deps) + return ( + other + and self.md5 == other.md5 + and self.python == other.python + and self.version == other.version + and self.sitepackages == other.sitepackages + and self.usedevelop == other.usedevelop + and self.alwayscopy == other.alwayscopy + and self.deps == other.deps + ) class VirtualEnv(object): + def __init__(self, envconfig=None, session=None): self.envconfig = envconfig self.session = session @@ -82,7 +89,7 @@ def name(self): return self.envconfig.envname def __repr__(self): - return "" % (self.path) + return "".format(self.path) def getcommandpath(self, name, venv=True, cwd=None): """ Return absolute path (str or localpath) for specified command name. @@ -105,8 +112,7 @@ def getcommandpath(self, name, venv=True, cwd=None): path = self._normal_lookup(name) if path is None: - raise tox.exception.InvocationError( - "could not find executable %r" % (name,)) + raise tox.exception.InvocationError("could not find executable {!r}".format(name)) return str(path) # will not be rewritten for reporting @@ -128,19 +134,18 @@ def _check_external_allowed_and_warn(self, path): if not self.is_allowed_external(path): self.session.report.warning( "test command found but not installed in testenv\n" - " cmd: %s\n" - " env: %s\n" + " cmd: {}\n" + " env: {}\n" "Maybe you forgot to specify a dependency? " - "See also the whitelist_externals envconfig setting." % ( - path, self.envconfig.envdir)) + "See also the whitelist_externals envconfig setting.".format( + path, self.envconfig.envdir + ) + ) def is_allowed_external(self, p): tryadd = [""] if tox.INFO.IS_WIN: - tryadd += [ - os.path.normcase(x) - for x in os.environ['PATHEXT'].split(os.pathsep) - ] + tryadd += [os.path.normcase(x) for x in os.environ["PATHEXT"].split(os.pathsep)] p = py.path.local(os.path.normcase(str(p))) for x in self.envconfig.whitelist_externals: for add in tryadd: @@ -153,8 +158,7 @@ def update(self, action): if status string is empty, all is ok. """ rconfig = CreationConfig.readconfig(self.path_config) - if not self.envconfig.recreate and rconfig and \ - rconfig.matches(self._getliveconfig()): + if not self.envconfig.recreate and rconfig and rconfig.matches(self._getliveconfig()): action.info("reusing", self.envconfig.envdir) return if rconfig is None: @@ -170,8 +174,7 @@ def update(self, action): self.hook.tox_testenv_install_deps(action=action, venv=self) except tox.exception.InvocationError: v = sys.exc_info()[1] - return "could not install deps %s; v = %r" % ( - self.envconfig.deps, v) + return "could not install deps {}; v = {!r}".format(self.envconfig.deps, v) def _getliveconfig(self): python = self.envconfig.python_info.executable @@ -185,8 +188,7 @@ def _getliveconfig(self): raw_dep = dep.name md5 = getdigest(raw_dep) deps.append((md5, raw_dep)) - return CreationConfig(md5, python, version, - sitepackages, develop, deps, alwayscopy) + return CreationConfig(md5, python, version, sitepackages, develop, deps, alwayscopy) def _getresolvedeps(self): deps = [] @@ -208,20 +210,19 @@ def finish(self): self._getliveconfig().writeconfig(self.path_config) def _needs_reinstall(self, setupdir, action): - setup_py = setupdir.join('setup.py') - setup_cfg = setupdir.join('setup.cfg') - args = [self.envconfig.envpython, str(setup_py), '--name'] + setup_py = setupdir.join("setup.py") + setup_cfg = setupdir.join("setup.cfg") + args = [self.envconfig.envpython, str(setup_py), "--name"] env = self._getenv() - output = action.popen(args, cwd=setupdir, redirect=False, - returnout=True, env=env) + output = action.popen(args, cwd=setupdir, redirect=False, returnout=True, env=env) name = output.strip() - args = [self.envconfig.envpython, '-c', 'import sys; print(sys.path)'] + args = [self.envconfig.envpython, "-c", "import sys; print(sys.path)"] out = action.popen(args, redirect=False, returnout=True, env=env) try: sys_path = ast.literal_eval(out.strip()) except SyntaxError: sys_path = [] - egg_info_fname = '.'.join((name, 'egg-info')) + egg_info_fname = ".".join((name, "egg-info")) for d in reversed(sys_path): egg_info = py.path.local(d).join(egg_info_fname) if egg_info.check(): @@ -235,7 +236,7 @@ def _needs_reinstall(self, setupdir, action): def developpkg(self, setupdir, action): assert action is not None - if getattr(self, 'just_created', False): + if getattr(self, "just_created", False): action.setactivity("develop-inst", setupdir) self.finish() extraopts = [] @@ -244,25 +245,25 @@ def developpkg(self, setupdir, action): action.setactivity("develop-inst-noop", setupdir) return action.setactivity("develop-inst-nodeps", setupdir) - extraopts = ['--no-deps'] + extraopts = ["--no-deps"] if action.venv.envconfig.extras: - setupdir += '[%s]' % ','.join(action.venv.envconfig.extras) + setupdir += "[{}]".format(",".join(action.venv.envconfig.extras)) - self._install(['-e', setupdir], extraopts=extraopts, action=action) + self._install(["-e", setupdir], extraopts=extraopts, action=action) def installpkg(self, sdistpath, action): assert action is not None - if getattr(self, 'just_created', False): + if getattr(self, "just_created", False): action.setactivity("inst", sdistpath) self.finish() extraopts = [] else: action.setactivity("inst-nodeps", sdistpath) - extraopts = ['-U', '--no-deps'] + extraopts = ["-U", "--no-deps"] if action.venv.envconfig.extras: - sdistpath += '[%s]' % ','.join(action.venv.envconfig.extras) + sdistpath += "[{}]".format(",".join(action.venv.envconfig.extras)) self._install([sdistpath], extraopts=extraopts, action=action) @@ -276,30 +277,33 @@ def _installopts(self, indexserver): def run_install_command(self, packages, action, options=()): argv = self.envconfig.install_command[:] - i = argv.index('{packages}') + i = argv.index("{packages}") argv[i:i + 1] = packages - if '{opts}' in argv: - i = argv.index('{opts}') + if "{opts}" in argv: + i = argv.index("{opts}") argv[i:i + 1] = list(options) - for x in ('PIP_RESPECT_VIRTUALENV', 'PIP_REQUIRE_VIRTUALENV', - '__PYVENV_LAUNCHER__'): + for x in ("PIP_RESPECT_VIRTUALENV", "PIP_REQUIRE_VIRTUALENV", "__PYVENV_LAUNCHER__"): os.environ.pop(x, None) - if 'PYTHONPATH' not in self.envconfig.passenv: + if "PYTHONPATH" not in self.envconfig.passenv: # If PYTHONPATH not explicitly asked for, remove it. - if 'PYTHONPATH' in os.environ: + if "PYTHONPATH" in os.environ: self.session.report.warning( "Discarding $PYTHONPATH from environment, to override " "specify PYTHONPATH in 'passenv' in your configuration." ) - os.environ.pop('PYTHONPATH') + os.environ.pop("PYTHONPATH") old_stdout = sys.stdout - sys.stdout = codecs.getwriter('utf8')(sys.stdout) + sys.stdout = codecs.getwriter("utf8")(sys.stdout) try: - self._pcall(argv, cwd=self.envconfig.config.toxinidir, - action=action, redirect=self.session.report.verbosity < 2) + self._pcall( + argv, + cwd=self.envconfig.config.toxinidir, + action=action, + redirect=self.session.report.verbosity < 2, + ) finally: sys.stdout = old_stdout @@ -313,7 +317,7 @@ def _install(self, deps, extraopts=None, action=None): dep = DepConfig(str(dep), None) assert isinstance(dep, DepConfig), dep if dep.indexserver is None: - ixserver = self.envconfig.config.indexserver['default'] + ixserver = self.envconfig.config.indexserver["default"] else: ixserver = dep.indexserver d.setdefault(ixserver, []).append(dep.name) @@ -326,8 +330,7 @@ def _install(self, deps, extraopts=None, action=None): options = self._installopts(ixserver.url) if extraopts: options.extend(extraopts) - self.run_install_command(packages=packages, options=options, - action=action) + self.run_install_command(packages=packages, options=options, action=action) def _getenv(self, testcommand=False): if testcommand: @@ -344,7 +347,7 @@ def _getenv(self, testcommand=False): # in any case we honor per-testenv setenv configuration env.update(self.envconfig.setenv) - env['VIRTUAL_ENV'] = str(self.path) + env["VIRTUAL_ENV"] = str(self.path) return env def test(self, redirect=False): @@ -356,12 +359,11 @@ def test(self, redirect=False): cwd = self.envconfig.changedir env = self._getenv(testcommand=True) # Display PYTHONHASHSEED to assist with reproducibility. - action.setactivity("runtests", "PYTHONHASHSEED=%r" % env.get('PYTHONHASHSEED')) + action.setactivity("runtests", "PYTHONHASHSEED={!r}".format(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 - message = "commands[%s] | %s" % (i, ' '.join( - [str(x) for x in argv])) + message = "commands[{}] | {}".format(i, " ".join([str(x) for x in argv])) action.setactivity("runtests", message) # check to see if we need to ignore the return code # if so, we need to alter the command line arguments @@ -375,13 +377,18 @@ def test(self, redirect=False): ignore_ret = False try: - self._pcall(argv, cwd=cwd, action=action, redirect=redirect, - ignore_ret=ignore_ret, testcommand=True) + self._pcall( + argv, + cwd=cwd, + action=action, + redirect=redirect, + ignore_ret=ignore_ret, + testcommand=True, + ) except tox.exception.InvocationError as err: if self.envconfig.ignore_outcome: - self.session.report.warning( - "command failed but result from testenv is ignored\n" - " cmd: %s" % (str(err),)) + msg = "command failed but result from testenv is ignored\ncmd:" + self.session.report.warning("{} {}".format(msg, err)) self.status = "ignored failed command" continue # keep processing commands @@ -394,20 +401,20 @@ def test(self, redirect=False): self.session.report.error(self.status) raise - def _pcall(self, args, cwd, venv=True, testcommand=False, - action=None, redirect=True, ignore_ret=False): - os.environ.pop('VIRTUALENV_PYTHON', None) + def _pcall( + self, args, cwd, venv=True, testcommand=False, action=None, redirect=True, ignore_ret=False + ): + os.environ.pop("VIRTUALENV_PYTHON", None) cwd.ensure(dir=1) args[0] = self.getcommandpath(args[0], venv, cwd) - if sys.platform != 'win32' and 'TOX_LIMITED_SHEBANG' in os.environ: + if sys.platform != "win32" and "TOX_LIMITED_SHEBANG" in os.environ: args = prepend_shebang_interpreter(args) env = self._getenv(testcommand=testcommand) bindir = str(self.envconfig.envbindir) - env['PATH'] = p = os.pathsep.join([bindir, os.environ["PATH"]]) - self.session.report.verbosity2("setting PATH=%s" % p) - return action.popen(args, cwd=cwd, env=env, - redirect=redirect, ignore_ret=ignore_ret) + env["PATH"] = p = os.pathsep.join([bindir, os.environ["PATH"]]) + self.session.report.verbosity2("setting PATH={}".format(p)) + return action.popen(args, cwd=cwd, env=env, redirect=redirect, ignore_ret=ignore_ret) def getdigest(path): @@ -430,8 +437,8 @@ def prepend_shebang_interpreter(args): # a maximum size of 2048 bytes to limit excessive reading and support UNIX # systems which may support a longer interpret length. try: - with open(args[0], 'rb') as f: - if f.read(1) == b'#' and f.read(1) == b'!': + with open(args[0], "rb") as f: + if f.read(1) == b"#" and f.read(1) == b"!": MAXINTERP = 2048 interp = f.readline(MAXINTERP).rstrip() interp_args = interp.split(None, 1)[:2] @@ -444,13 +451,13 @@ def prepend_shebang_interpreter(args): @tox.hookimpl def tox_testenv_create(venv, action): config_interpreter = venv.getsupportedinterpreter() - args = [sys.executable, '-m', 'virtualenv'] + args = [sys.executable, "-m", "virtualenv"] if venv.envconfig.sitepackages: - args.append('--system-site-packages') + args.append("--system-site-packages") if venv.envconfig.alwayscopy: - args.append('--always-copy') + args.append("--always-copy") # add interpreter explicitly, to prevent using default (virtualenv.ini) - args.extend(['--python', str(config_interpreter)]) + args.extend(["--python", str(config_interpreter)]) venv.session.make_emptydir(venv.path) basepath = venv.path.dirpath() basepath.ensure(dir=1) @@ -464,7 +471,7 @@ def tox_testenv_install_deps(venv, action): deps = venv._getresolvedeps() if deps: depinfo = ", ".join(map(str, deps)) - action.setactivity("installdeps", "%s" % depinfo) + action.setactivity("installdeps", depinfo) venv._install(deps, action=action) return True # Return non-None to indicate plugin has completed @@ -479,9 +486,7 @@ def tox_runtest(venv, redirect): def tox_runenvreport(venv, action): # write out version dependency information args = venv.envconfig.list_dependencies_command - output = venv._pcall(args, - cwd=venv.envconfig.config.toxinidir, - action=action) + output = venv._pcall(args, cwd=venv.envconfig.config.toxinidir, action=action) # the output contains a mime-header, skip it output = output.split("\n\n")[-1] packages = output.strip().split("\n")