diff options
Diffstat (limited to 'tox')
-rw-r--r-- | tox/__init__.py | 33 | ||||
-rw-r--r-- | tox/__main__.py | 4 | ||||
-rw-r--r-- | tox/_pytestplugin.py | 333 | ||||
-rw-r--r-- | tox/_quickstart.py | 275 | ||||
-rw-r--r-- | tox/_verlib.py | 335 | ||||
-rw-r--r-- | tox/config.py | 1247 | ||||
-rw-r--r-- | tox/hookspecs.py | 59 | ||||
-rw-r--r-- | tox/interpreters.py | 184 | ||||
-rw-r--r-- | tox/result.py | 81 | ||||
-rw-r--r-- | tox/session.py | 682 | ||||
-rw-r--r-- | tox/venv.py | 416 |
11 files changed, 0 insertions, 3649 deletions
diff --git a/tox/__init__.py b/tox/__init__.py deleted file mode 100644 index baf651b..0000000 --- a/tox/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -__version__ = '2.4.0.dev1' - -from .hookspecs import hookspec, hookimpl # noqa - - -class exception: - class Error(Exception): - def __str__(self): - return "%s: %s" % (self.__class__.__name__, self.args[0]) - - class ConfigError(Error): - """ error in tox configuration. """ - class UnsupportedInterpreter(Error): - "signals an unsupported Interpreter" - class InterpreterNotFound(Error): - "signals that an interpreter could not be found" - class InvocationError(Error): - """ an error while invoking a script. """ - class MissingFile(Error): - """ an error while invoking a script. """ - class MissingDirectory(Error): - """ a directory did not exist. """ - class MissingDependency(Error): - """ a dependency could not be found or determined. """ - class MinVersionError(Error): - """ the installed tox version is lower than requested minversion. """ - - def __init__(self, message): - self.message = message - super(exception.MinVersionError, self).__init__(message) - -from tox.session import main as cmdline # noqa diff --git a/tox/__main__.py b/tox/__main__.py deleted file mode 100644 index e28d746..0000000 --- a/tox/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from tox.session import main - -if __name__ == "__main__": - main() diff --git a/tox/_pytestplugin.py b/tox/_pytestplugin.py deleted file mode 100644 index 785070d..0000000 --- a/tox/_pytestplugin.py +++ /dev/null @@ -1,333 +0,0 @@ -import py -import pytest -import tox -import os -import sys -from py.builtin import _isbytes, _istext, print_ -from fnmatch import fnmatch -import time -from .config import parseconfig -from .venv import VirtualEnv -from .session import Action -from .result import ResultLog - - -def pytest_configure(): - 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") - - -def pytest_report_header(): - return "tox comes from: %r" % (tox.__file__) - - -@pytest.fixture -def newconfig(request, tmpdir): - def newconfig(args, source=None, plugins=()): - if source is None: - source = args - args = [] - s = py.std.textwrap.dedent(source) - p = tmpdir.join("tox.ini") - p.write(s) - old = tmpdir.chdir() - try: - return parseconfig(args, plugins=plugins) - finally: - old.chdir() - return newconfig - - -@pytest.fixture -def cmd(request): - if request.config.option.no_network: - pytest.skip("--no-network was specified, test cannot run") - return Cmd(request) - - -class ReportExpectMock: - def __init__(self, session): - self._calls = [] - self._index = -1 - self.session = session - - def clear(self): - self._calls[:] = [] - - def __getattr__(self, name): - if name[0] == "_": - raise AttributeError(name) - - def generic_report(*args, **kwargs): - self._calls.append((name,) + args) - print("%s" % (self._calls[-1], )) - return generic_report - - def action(self, venv, msg, *args): - self._calls.append(("action", venv, msg)) - print("%s" % (self._calls[-1], )) - return Action(self.session, venv, msg, args) - - def getnext(self, cat): - __tracebackhide__ = True - newindex = self._index + 1 - while newindex < len(self._calls): - call = self._calls[newindex] - lcat = call[0] - if fnmatch(lcat, cat): - self._index = newindex - return call - newindex += 1 - raise LookupError( - "looking for %r, no reports found at >=%d in %r" % - (cat, self._index + 1, self._calls)) - - def expect(self, cat, messagepattern="*", invert=False): - __tracebackhide__ = True - if not messagepattern.startswith("*"): - messagepattern = "*" + messagepattern - while self._index < len(self._calls): - try: - call = self.getnext(cat) - except LookupError: - break - for lmsg in call[1:]: - lmsg = str(lmsg).replace("\n", " ") - if fnmatch(lmsg, messagepattern): - if invert: - raise AssertionError("found %s(%r), didn't expect it" % - (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)) - - 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:] - self.cwd = cwd - self.env = env - self.stdout = stdout - self.stderr = stderr - self.shell = shell - - def communicate(self): - return "", "" - - def wait(self): - pass - - -@pytest.fixture -def mocksession(request): - from tox.session import Session - - class MockSession(Session): - def __init__(self): - self._clearmocks() - self.config = request.getfuncargvalue("newconfig")([], "") - self.resultlog = ResultLog() - self._actions = [] - - def getenv(self, name): - return VirtualEnv(self.config.envconfigs[name], session=self) - - def _clearmocks(self): - self._pcalls = [] - self._spec2pkg = {} - self.report = ReportExpectMock(self) - - def make_emptydir(self, path): - pass - - def popen(self, args, cwd, shell=None, - universal_newlines=False, - 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(request): - mocksession = request.getfuncargvalue("mocksession") - newconfig = request.getfuncargvalue("newconfig") - - def newmocksession(args, source, plugins=()): - mocksession.config = newconfig(args, source, plugins=plugins) - return mocksession - return newmocksession - - -class Cmd: - def __init__(self, request): - self.tmpdir = request.getfuncargvalue("tmpdir") - self.request = request - current = py.path.local() - self.request.addfinalizer(current.chdir) - - def chdir(self, target): - target.chdir() - - def popen(self, argv, stdout, stderr, **kw): - if not hasattr(py.std, 'subprocess'): - py.test.skip("no subprocess module") - env = os.environ.copy() - env['PYTHONPATH'] = ":".join(filter(None, [ - str(os.getcwd()), env.get('PYTHONPATH', '')])) - kw['env'] = env - # print "env", env - return py.std.subprocess.Popen(argv, stdout=stdout, stderr=stderr, **kw) - - def run(self, *argv): - if argv[0] == "tox" and sys.version_info[:2] < (2, 7): - pytest.skip("can not run tests involving calling tox on python2.6. " - "(and python2.6 is about to be deprecated anyway)") - argv = [str(x) for x in argv] - assert py.path.local.sysfind(str(argv[0])), argv[0] - p1 = self.tmpdir.join("stdout") - p2 = self.tmpdir.join("stderr") - print("%s$ %s" % (os.getcwd(), " ".join(argv))) - f1 = p1.open("wb") - f2 = p2.open("wb") - now = time.time() - popen = self.popen(argv, stdout=f1, stderr=f2, - close_fds=(sys.platform != "win32")) - ret = popen.wait() - f1.close() - f2.close() - out = p1.read("rb") - out = getdecoded(out).splitlines() - err = p2.read("rb") - err = getdecoded(err).splitlines() - - def dump_lines(lines, fp): - try: - for line in lines: - py.builtin.print_(line, file=fp) - except UnicodeEncodeError: - print("couldn't print to %s because of encoding" % (fp,)) - dump_lines(out, sys.stdout) - dump_lines(err, sys.stderr) - return RunResult(ret, out, err, time.time() - now) - - -def getdecoded(out): - try: - return out.decode("utf-8") - except UnicodeDecodeError: - return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % ( - py.io.saferepr(out),) - - -class RunResult: - def __init__(self, ret, outlines, errlines, duration): - self.ret = ret - self.outlines = outlines - self.errlines = errlines - self.stdout = LineMatcher(outlines) - self.stderr = LineMatcher(errlines) - self.duration = duration - - -class LineMatcher: - def __init__(self, lines): - self.lines = lines - - def str(self): - return "\n".join(self.lines) - - def fnmatch_lines(self, lines2): - if isinstance(lines2, str): - lines2 = py.code.Source(lines2) - if isinstance(lines2, py.code.Source): - lines2 = lines2.strip().lines - - from fnmatch import fnmatch - lines1 = self.lines[:] - nextline = None - extralines = [] - __tracebackhide__ = True - for line in lines2: - nomatchprinted = False - while lines1: - nextline = lines1.pop(0) - if line == nextline: - print_("exact match:", repr(line)) - break - elif fnmatch(nextline, line): - print_("fnmatch:", repr(line)) - print_(" with:", repr(nextline)) - break - else: - if not nomatchprinted: - print_("nomatch:", repr(line)) - nomatchprinted = True - print_(" and:", repr(nextline)) - extralines.append(nextline) - else: - assert line == nextline - - -@pytest.fixture -def initproj(request, tmpdir): - """ create a factory function for creating example projects. """ - def initproj(nameversion, filedefs=None): - if filedefs is None: - filedefs = {} - if _istext(nameversion) or _isbytes(nameversion): - parts = nameversion.split("-") - if len(parts) == 1: - parts.append("0.1") - name, version = parts - else: - name, version = nameversion - base = tmpdir.ensure(name, dir=1) - create_files(base, filedefs) - if 'setup.py' not in filedefs: - create_files(base, {'setup.py': ''' - from setuptools import setup - setup( - name='%(name)s', - description='%(name)s project', - version='%(version)s', - license='MIT', - platforms=['unix', 'win32'], - packages=['%(name)s', ], - ) - ''' % locals()}) - if name not in filedefs: - create_files(base, { - name: {'__init__.py': '__version__ = %r' % version} - }) - manifestlines = [] - for p in base.visit(lambda x: x.check(file=1)): - manifestlines.append("include %s" % p.relto(base)) - create_files(base, {"MANIFEST.in": "\n".join(manifestlines)}) - print("created project in %s" % (base,)) - base.chdir() - return initproj - - -def create_files(base, filedefs): - for key, value in filedefs.items(): - if isinstance(value, dict): - create_files(base.ensure(key, dir=1), value) - elif isinstance(value, str): - s = py.std.textwrap.dedent(value) - base.join(key).write(s) diff --git a/tox/_quickstart.py b/tox/_quickstart.py deleted file mode 100644 index 59ee48e..0000000 --- a/tox/_quickstart.py +++ /dev/null @@ -1,275 +0,0 @@ -# -*- coding: utf-8 -*- -""" - tox._quickstart - ~~~~~~~~~~~~~~~~~ - - Command-line script to quickly setup tox.ini for a Python project - - This file was heavily inspired by and uses code from ``sphinx-quickstart`` - in the BSD-licensed `Sphinx project`_. - - .. Sphinx project_: http://sphinx.pocoo.org/ - - License for Sphinx - ================== - - Copyright (c) 2007-2011 by the Sphinx team (see AUTHORS file). - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" - -import sys -from os import path -from codecs import open - -TERM_ENCODING = getattr(sys.stdin, 'encoding', None) - -from tox import __version__ - -# function to get input from terminal -- overridden by the test suite -try: - # this raw_input is not converted by 2to3 - term_input = raw_input -except NameError: - term_input = input - - -all_envs = ['py26', 'py27', 'py32', 'py33', 'py34', 'py35', 'pypy', 'jython'] - -PROMPT_PREFIX = '> ' - -QUICKSTART_CONF = '''\ -# Tox (http://tox.testrun.org/) 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" -# and then run "tox" from this directory. - -[tox] -envlist = %(envlist)s - -[testenv] -commands = %(commands)s -deps = %(deps)s -''' - - -class ValidationError(Exception): - """Raised for validation errors.""" - - -def nonempty(x): - if not x: - raise ValidationError("Please enter some text.") - return x - - -def choice(*l): - def val(x): - if x not in l: - raise ValidationError('Please enter one of %s.' % ', '.join(l)) - return x - return val - - -def boolean(x): - if x.upper() not in ('Y', 'YES', 'N', 'NO'): - raise ValidationError("Please enter either 'y' or 'n'.") - 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'.") - return x - - -def ok(x): - return x - - -def do_prompt(d, key, text, default=None, validator=nonempty): - while True: - if default: - prompt = PROMPT_PREFIX + '%s [%s]: ' % (text, default) - else: - prompt = PROMPT_PREFIX + text + ': ' - x = term_input(prompt) - if default and not x: - x = default - if sys.version_info < (3, ) and not isinstance(x, unicode): # noqa - # for Python 2.x, try to get a Unicode string out of it - if x.decode('ascii', 'replace').encode('ascii', 'replace') != x: - if TERM_ENCODING: - x = x.decode(TERM_ENCODING) - else: - print('* Note: non-ASCII characters entered ' - 'and terminal encoding unknown -- assuming ' - 'UTF-8 or Latin-1.') - try: - x = x.decode('utf-8') - except UnicodeDecodeError: - x = x.decode('latin1') - try: - x = validator(x) - except ValidationError: - err = sys.exc_info()[1] - print('* ' + str(err)) - continue - break - d[key] = x - - -def ask_user(d): - """Ask the user for quickstart values missing from *d*. - - """ - - print('Welcome to the Tox %s quickstart utility.' % __version__) - print(''' -This utility will ask you a few questions and then generate a simple tox.ini -file to help get you started using tox. - -Please enter values for the following settings (just press Enter to -accept a default value, if one is given in brackets).''') - - sys.stdout.write('\n') - - print(''' -What Python versions do you want to test against? Choices: - [1] py27 - [2] py27, py33 - [3] (All versions) %s - [4] Choose each one-by-one''' % ', '.join(all_envs)) - do_prompt(d, 'canned_pyenvs', 'Enter the number of your choice', - '3', choice('1', '2', '3', '4')) - - if d['canned_pyenvs'] == '1': - d['py27'] = True - elif d['canned_pyenvs'] == '2': - for pyenv in ('py27', 'py33'): - d[pyenv] = True - elif d['canned_pyenvs'] == '3': - for pyenv in all_envs: - d[pyenv] = True - elif d['canned_pyenvs'] == '4': - for pyenv in all_envs: - if pyenv not in d: - do_prompt(d, pyenv, 'Test your project with %s (Y/n)' % pyenv, 'Y', boolean) - - print(''' -What command should be used to test your project -- examples: - - py.test - - python setup.py test - - nosetests package.module - - trial package.module''') - do_prompt(d, 'commands', 'Command to run to test project', '{envpython} setup.py test') - - default_deps = ' ' - if 'py.test' in d['commands']: - default_deps = 'pytest' - if 'nosetests' in d['commands']: - default_deps = 'nose' - if 'trial' in d['commands']: - default_deps = 'twisted' - - print(''' -What extra dependencies do your tests have?''') - do_prompt(d, 'deps', 'Comma-separated list of dependencies', default_deps) - - -def process_input(d): - d['envlist'] = ', '.join([env for env in all_envs if d.get(env) is True]) - d['deps'] = '\n' + '\n'.join([ - ' %s' % dep.strip() - for dep in d['deps'].split(',')]) - - return d - - -def rtrim_right(text): - lines = [] - for line in text.split("\n"): - lines.append(line.rstrip()) - return "\n".join(lines) - - -def generate(d, overwrite=True, silent=False): - """Generate project based on values in *d*.""" - - conf_text = QUICKSTART_CONF % d - conf_text = rtrim_right(conf_text) - - def write_file(fpath, mode, content): - print('Creating file %s.' % fpath) - f = open(fpath, mode, encoding='utf-8') - try: - f.write(content) - finally: - f.close() - - sys.stdout.write('\n') - - fpath = 'tox.ini' - - if path.isfile(fpath) and not overwrite: - print('File %s already exists.' % fpath) - do_prompt(d, 'fpath', 'Alternative path to write tox.ini contents to', 'tox-generated.ini') - fpath = d['fpath'] - - write_file(fpath, 'w', conf_text) - - if silent: - return - sys.stdout.write('\n') - print('Finished: A tox.ini file has been created. For information on this file, ' - 'see http://tox.testrun.org/latest/config.html') - print(''' -Execute `tox` to test your project. -''') - - -def main(argv=sys.argv): - d = {} - - if len(argv) > 3: - print('Usage: tox-quickstart [root]') - sys.exit(1) - elif len(argv) == 2: - d['path'] = argv[1] - - try: - ask_user(d) - except (KeyboardInterrupt, EOFError): - print() - print('[Interrupted.]') - return - - d = process_input(d) - generate(d, overwrite=False) - - -if __name__ == '__main__': - main() diff --git a/tox/_verlib.py b/tox/_verlib.py deleted file mode 100644 index a2e7471..0000000 --- a/tox/_verlib.py +++ /dev/null @@ -1,335 +0,0 @@ -""" - -PEP386-version comparison algorithm. - -(c) Tarek Ziade and others -extracted unmodified from https://bitbucket.org/tarek/distutilsversion -licensed under the PSF license (i guess) - -""" - -import re - - -class IrrationalVersionError(Exception): - """This is an irrational version.""" - pass - - -class HugeMajorVersionNumError(IrrationalVersionError): - """An irrational version because the major version number is huge - (often because a year or date was used). - - See `error_on_huge_major_num` option in `NormalizedVersion` for details. - This guard can be disabled by setting that option False. - """ - pass - -# A marker used in the second and third parts of the `parts` tuple, for -# versions that don't have those segments, to sort properly. An example -# of versions in sort order ('highest' last): -# 1.0b1 ((1,0), ('b',1), ('f',)) -# 1.0.dev345 ((1,0), ('f',), ('dev', 345)) -# 1.0 ((1,0), ('f',), ('f',)) -# 1.0.post256.dev345 ((1,0), ('f',), ('f', 'post', 256, 'dev', 345)) -# 1.0.post345 ((1,0), ('f',), ('f', 'post', 345, 'f')) -# ^ ^ ^ -# 'b' < 'f' ---------------------/ | | -# | | -# 'dev' < 'f' < 'post' -------------------/ | -# | -# 'dev' < 'f' ----------------------------------------------/ -# Other letters would do, but 'f' for 'final' is kind of nice. -FINAL_MARKER = ('f',) - -VERSION_RE = re.compile(r''' - ^ - (?P<version>\d+\.\d+) # minimum 'N.N' - (?P<extraversion>(?:\.\d+)*) # any number of extra '.N' segments - (?: - (?P<prerel>[abc]|rc) # 'a'=alpha, 'b'=beta, 'c'=release candidate - # 'rc'= alias for release candidate - (?P<prerelversion>\d+(?:\.\d+)*) - )? - (?P<postdev>(\.post(?P<post>\d+))?(\.dev(?P<dev>\d+))?)? - $''', re.VERBOSE) - - -class NormalizedVersion(object): - """A rational version. - - Good: - 1.2 # equivalent to "1.2.0" - 1.2.0 - 1.2a1 - 1.2.3a2 - 1.2.3b1 - 1.2.3c1 - 1.2.3.4 - TODO: fill this out - - Bad: - 1 # mininum two numbers - 1.2a # release level must have a release serial - 1.2.3b - """ - def __init__(self, s, error_on_huge_major_num=True): - """Create a NormalizedVersion instance from a version string. - - @param s {str} The version string. - @param error_on_huge_major_num {bool} Whether to consider an - apparent use of a year or full date as the major version number - an error. Default True. One of the observed patterns on PyPI before - the introduction of `NormalizedVersion` was version numbers like this: - 2009.01.03 - 20040603 - 2005.01 - This guard is here to strongly encourage the package author to - use an alternate version, because a release deployed into PyPI - and, e.g. downstream Linux package managers, will forever remove - the possibility of using a version number like "1.0" (i.e. - where the major number is less than that huge major number). - """ - self._parse(s, error_on_huge_major_num) - - @classmethod - 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): - """Parses a string version into parts.""" - match = VERSION_RE.search(s) - if not match: - raise IrrationalVersionError(s) - - groups = match.groupdict() - 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(extraversion[1:], s) - parts.append(tuple(block)) - - # prerelease - prerel = groups.get('prerel') - if prerel is not None: - block = [prerel] - 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') - postdev = [] - if post is not None: - 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)]) - 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)) - - 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 - @param full_ver_str {str} The full version string from which this - comes. Used for error strings. - @param drop_trailing_zeros {bool} Whether to drop trailing zeros - from the returned list. Default True. - @param pad_zeros_length {int} The length to which to pad the - returned list with zeros, if necessary. Default 0. - """ - nums = [] - for n in s.split("."): - 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)) - nums.append(int(n)) - if drop_trailing_zeros: - while nums and nums[-1] == 0: - nums.pop() - while len(nums) < pad_zeros_length: - nums.append(0) - return nums - - def __str__(self): - return self.parts_to_str(self.parts) - - @classmethod - def parts_to_str(cls, parts): - """Transforms a version expressed in tuple into its string - representation.""" - # XXX This doesn't check for invalid tuples - main, prerel, postdev = parts - 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:]) - if postdev and postdev is not FINAL_MARKER: - if postdev[0] == 'f': - postdev = postdev[1:] - i = 0 - while i < len(postdev): - if i % 2 == 0: - s += '.' - s += str(postdev[i]) - i += 1 - return s - - def __repr__(self): - return "%s('%s')" % (self.__class__.__name__, self) - - def _cannot_compare(self, other): - raise TypeError("cannot compare %s and %s" - % (type(self).__name__, type(other).__name__)) - - def __eq__(self, other): - if not isinstance(other, NormalizedVersion): - self._cannot_compare(other) - return self.parts == other.parts - - def __lt__(self, other): - if not isinstance(other, NormalizedVersion): - self._cannot_compare(other) - return self.parts < other.parts - - def __ne__(self, other): - return not self.__eq__(other) - - def __gt__(self, other): - return not (self.__lt__(other) or self.__eq__(other)) - - def __le__(self, other): - return self.__eq__(other) or self.__lt__(other) - - def __ge__(self, other): - return self.__eq__(other) or self.__gt__(other) - - -def suggest_normalized_version(s): - """Suggest a normalized version close to the given version string. - - If you have a version string that isn't rational (i.e. NormalizedVersion - doesn't like it) then you might be able to get an equivalent (or close) - rational version from this function. - - This does a number of simple normalizations to the given string, based - on observation of versions currently in use on PyPI. Given a dump of - those version during PyCon 2009, 4287 of them: - - 2312 (53.93%) match NormalizedVersion without change - - with the automatic suggestion - - 3474 (81.04%) match when using this suggestion method - - @param s {str} An irrational version string. - @returns A rational version string, or None, if couldn't determine one. - """ - try: - NormalizedVersion(s) - return s # already rational - except IrrationalVersionError: - pass - - rs = s.lower() - - # part of this could use maketrans - for orig, repl in (('-alpha', 'a'), ('-beta', 'b'), ('alpha', 'a'), - ('beta', 'b'), ('rc', 'c'), ('-final', ''), - ('-pre', 'c'), - ('-release', ''), ('.release', ''), ('-stable', ''), - ('+', '.'), ('_', '.'), (' ', ''), ('.final', ''), - ('final', '')): - rs = rs.replace(orig, repl) - - # if something ends with dev or pre, we add a 0 - rs = re.sub(r"pre$", r"pre0", rs) - rs = re.sub(r"dev$", r"dev0", rs) - - # if we have something like "b-2" or "a.2" at the end of the - # version, that is pobably beta, alpha, etc - # let's remove the dash or dot - rs = re.sub(r"([abc|rc])[\-\.](\d+)$", r"\1\2", rs) - - # 1.0-dev-r371 -> 1.0.dev371 - # 0.1-dev-r79 -> 0.1.dev79 - rs = re.sub(r"[\-\.](dev)[\-\.]?r?(\d+)$", r".\1\2", rs) - - # Clean: 2.0.a.3, 2.0.b1, 0.9.0~c1 - rs = re.sub(r"[.~]?([abc])\.?", r"\1", rs) - - # Clean: v0.3, v1.0 - if rs.startswith('v'): - rs = rs[1:] - - # Clean leading '0's on numbers. - # TODO: unintended side-effect on, e.g., "2003.05.09" - # PyPI stats: 77 (~2%) better - rs = re.sub(r"\b0+(\d+)(?!\d)", r"\1", rs) - - # Clean a/b/c with no version. E.g. "1.0a" -> "1.0a0". Setuptools infers - # zero. - # PyPI stats: 245 (7.56%) better - rs = re.sub(r"(\d+[abc])$", r"\g<1>0", rs) - - # the 'dev-rNNN' tag is a dev tag - rs = re.sub(r"\.?(dev-r|dev\.r)\.?(\d+)$", r".dev\2", rs) - - # clean the - when used as a pre delimiter - rs = re.sub(r"-(a|b|c)(\d+)$", r"\1\2", rs) - - # a terminal "dev" or "devel" can be changed into ".dev0" - rs = re.sub(r"[\.\-](dev|devel)$", r".dev0", rs) - - # a terminal "dev" can be changed into ".dev0" - rs = re.sub(r"(?![\.\-])dev$", r".dev0", rs) - - # a terminal "final" or "stable" can be removed - rs = re.sub(r"(final|stable)$", "", rs) - - # The 'r' and the '-' tags are post release tags - # 0.4a1.r10 -> 0.4a1.post10 - # 0.9.33-17222 -> 0.9.3.post17222 - # 0.9.33-r17222 -> 0.9.3.post17222 - rs = re.sub(r"\.?(r|-|-r)\.?(\d+)$", r".post\2", rs) - - # Clean 'r' instead of 'dev' usage: - # 0.9.33+r17222 -> 0.9.3.dev17222 - # 1.0dev123 -> 1.0.dev123 - # 1.0.git123 -> 1.0.dev123 - # 1.0.bzr123 -> 1.0.dev123 - # 0.1a0dev.123 -> 0.1a0.dev123 - # PyPI stats: ~150 (~4%) better - rs = re.sub(r"\.?(dev|git|bzr)\.?(\d+)$", r".dev\2", rs) - - # Clean '.pre' (normalized from '-pre' above) instead of 'c' usage: - # 0.2.pre1 -> 0.2c1 - # 0.2-c1 -> 0.2c1 - # 1.0preview123 -> 1.0c123 - # PyPI stats: ~21 (0.62%) better - rs = re.sub(r"\.?(pre|preview|-c)(\d+)$", r"c\g<2>", rs) - - # Tcl/Tk uses "px" for their post release markers - rs = re.sub(r"p(\d+)$", r".post\1", rs) - - try: - NormalizedVersion(rs) - return rs # already rational - except IrrationalVersionError: - pass - return None diff --git a/tox/config.py b/tox/config.py deleted file mode 100644 index d6b4c52..0000000 --- a/tox/config.py +++ /dev/null @@ -1,1247 +0,0 @@ -import argparse -import os -import random -from fnmatch import fnmatchcase -import sys -import re -import shlex -import string -import pkg_resources -import itertools -import pluggy - -import tox.interpreters -from tox import hookspecs -from tox._verlib import NormalizedVersion - -import py - -import tox - -iswin32 = sys.platform == "win32" - -default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3', - 'py': sys.executable} -for version in '26,27,32,33,34,35,36'.split(','): - default_factors['py' + version] = 'python%s.%s' % tuple(version) - -hookimpl = pluggy.HookimplMarker("tox") - -_dummy = object() - - -def get_plugin_manager(plugins=()): - # initialize plugin manager - import tox.venv - pm = pluggy.PluginManager("tox") - pm.add_hookspecs(hookspecs) - pm.register(tox.config) - pm.register(tox.interpreters) - pm.register(tox.venv) - pm.register(tox.session) - pm.load_setuptools_entrypoints("tox") - for plugin in plugins: - pm.register(plugin) - pm.check_pending() - return pm - - -class Parser: - """ command line and ini-parser control object. """ - - def __init__(self): - self.argparser = argparse.ArgumentParser( - description="tox options", add_help=False) - self._testenv_attr = [] - - def add_argument(self, *args, **kwargs): - """ add argument to command line parser. This takes the - same arguments that ``argparse.ArgumentParser.add_argument``. - """ - return self.argparser.add_argument(*args, **kwargs) - - def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): - """ add an ini-file variable for "testenv" section. - - Types are specified as strings like "bool", "line-list", "string", "argv", "path", - "argvlist". - - The ``postprocess`` function will be called for each testenv - like ``postprocess(testenv_config=testenv_config, value=value)`` - where ``value`` is the value as read from the ini (or the default value) - and ``testenv_config`` is a :py:class:`tox.config.TestenvConfig` instance - which will receive all ini-variables as object attributes. - - Any postprocess function must return a value which will then be set - as the final value in the testenv section. - """ - self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) - - def add_testenv_attribute_obj(self, obj): - """ add an ini-file variable as an object. - - This works as the ``add_testenv_attribute`` function but expects - "name", "type", "help", and "postprocess" attributes on the object. - """ - assert hasattr(obj, "name") - assert hasattr(obj, "type") - assert hasattr(obj, "help") - assert hasattr(obj, "postprocess") - self._testenv_attr.append(obj) - - def _parse_args(self, args): - return self.argparser.parse_args(args) - - def _format_help(self): - return self.argparser.format_help() - - -class VenvAttribute: - def __init__(self, name, type, default, help, postprocess): - self.name = name - self.type = type - self.default = default - self.help = help - self.postprocess = postprocess - - -class DepOption: - name = "deps" - type = "line-list" - help = "each line specifies a dependency in pip/setuptools format." - default = () - - def postprocess(self, testenv_config, value): - deps = [] - config = testenv_config.config - for depline in value: - m = re.match(r":(\w+):\s*(\S+)", depline) - if m: - iname, name = m.groups() - ixserver = config.indexserver[iname] - else: - name = depline.strip() - ixserver = None - name = self._replace_forced_dep(name, config) - deps.append(DepConfig(name, ixserver)) - return deps - - def _replace_forced_dep(self, name, config): - """ - Override the given dependency config name taking --force-dep-version - option into account. - - :param name: dep config, for example ["pkg==1.0", "other==2.0"]. - :param config: Config instance - :return: the new dependency that should be used for virtual environments - """ - if not config.option.force_dep: - return name - for forced_dep in config.option.force_dep: - if self._is_same_dep(forced_dep, name): - return forced_dep - return name - - @classmethod - def _is_same_dep(cls, dep1, dep2): - """ - Returns True if both dependency definitions refer to the - same package, even if versions differ. - """ - dep1_name = pkg_resources.Requirement.parse(dep1).project_name - try: - dep2_name = pkg_resources.Requirement.parse(dep2).project_name - except pkg_resources.RequirementParseError: - # we couldn't parse a version, probably a URL - return False - return dep1_name == dep2_name - - -class PosargsOption: - name = "args_are_paths" - type = "bool" - default = True - help = "treat positional args in commands as paths" - - def postprocess(self, testenv_config, value): - config = testenv_config.config - args = config.option.args - if args: - if value: - args = [] - for arg in config.option.args: - if arg: - origpath = config.invocationcwd.join(arg, abs=True) - if origpath.check(): - arg = testenv_config.changedir.bestrelpath(origpath) - args.append(arg) - testenv_config._reader.addsubstitutions(args) - return value - - -class InstallcmdOption: - name = "install_command" - type = "argv" - default = "pip install {opts} {packages}" - help = "install command for dependencies and package under test." - - def postprocess(self, testenv_config, value): - if '{packages}' not in value: - raise tox.exception.ConfigError( - "'install_command' must contain '{packages}' substitution") - return value - - -def parseconfig(args=None, plugins=()): - """ - :param list[str] args: Optional list of arguments. - :type pkg: str - :rtype: :class:`Config` - :raise SystemExit: toxinit file is not found - """ - - pm = get_plugin_manager(plugins) - - if args is None: - args = sys.argv[1:] - - # prepare command line options - parser = Parser() - pm.hook.tox_addoption(parser=parser) - - # parse command line options - option = parser._parse_args(args) - interpreters = tox.interpreters.Interpreters(hook=pm.hook) - config = Config(pluginmanager=pm, option=option, interpreters=interpreters) - config._parser = parser - config._testenv_attr = parser._testenv_attr - - # parse ini file - basename = config.option.configfile - if os.path.isabs(basename): - inipath = py.path.local(basename) - 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') - if not inipath.check(): - feedback("toxini file %r not found" % (basename), sysexit=True) - - try: - parseini(config, inipath) - except tox.exception.InterpreterNotFound: - exn = sys.exc_info()[1] - # Use stdout to match test expectations - py.builtin.print_("ERROR: " + str(exn)) - - # post process config object - pm.hook.tox_configure(config=config) - - return config - - -def feedback(msg, sysexit=False): - py.builtin.print_("ERROR: " + msg, file=sys.stderr) - if sysexit: - raise SystemExit(1) - - -class VersionAction(argparse.Action): - def __call__(self, argparser, *args, **kwargs): - version = tox.__version__ - py.builtin.print_("%s imported from %s" % (version, tox.__file__)) - raise SystemExit(0) - - -class CountAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - if hasattr(namespace, self.dest): - setattr(namespace, self.dest, int(getattr(namespace, self.dest)) + 1) - else: - setattr(namespace, self.dest, 0) - - -class SetenvDict: - def __init__(self, dict, reader): - self.reader = reader - self.definitions = dict - self.resolved = {} - self._lookupstack = [] - - def __contains__(self, name): - return name in self.definitions - - def get(self, name, default=None): - try: - return self.resolved[name] - except KeyError: - try: - if name in self._lookupstack: - raise KeyError(name) - val = self.definitions[name] - except KeyError: - return os.environ.get(name, default) - self._lookupstack.append(name) - try: - self.resolved[name] = res = self.reader._replace(val) - finally: - self._lookupstack.pop() - return res - - def __getitem__(self, name): - x = self.get(name, _dummy) - if x is _dummy: - raise KeyError(name) - return x - - def keys(self): - return self.definitions.keys() - - def __setitem__(self, name, value): - self.definitions[name] = value - self.resolved[name] = value - - -@hookimpl -def tox_addoption(parser): - # formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("--version", nargs=0, action=VersionAction, - 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", nargs=0, action=CountAction, default=0, - dest="verbosity", - help="increase verbosity of 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") - parser.add_argument("-c", action="store", default="tox.ini", - dest="configfile", - help="use the specified config file name.") - 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', 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("--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}", - help="venv directory") - - # 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 - return setenv - - parser.add_testenv_attribute( - 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: - for f in testenv_config.factors: - if f in default_factors: - return default_factors[f] - return sys.executable - 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.") - - parser.add_testenv_attribute( - 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") - - def downloadcache(testenv_config, value): - if value: - # env var, if present, takes precedence - downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", value) - return py.path.local(downloadcache) - - parser.add_testenv_attribute( - name="downloadcache", type="string", default=None, postprocess=downloadcache, - help="(deprecated) set PIP_DOWNLOAD_CACHE.") - - parser.add_testenv_attribute( - 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") - - 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.") - - def recreate(testenv_config, value): - if testenv_config.config.option.recreate: - return True - return value - - parser.add_testenv_attribute( - 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])) - - passenv = set(["PATH", "PIP_INDEX_URL", "LANG", "LD_LIBRARY_PATH"]) - - # read in global passenv settings - p = os.environ.get("TOX_TESTENV_PASSENV", None) - if p is not None: - passenv.update(x for x in p.split() if x) - - # we ensure that tmp directory settings are passed on - # we could also set it to the per-venv "envtmpdir" - # but this leads to very long paths when run with jenkins - # so we just pass it on by default for now. - if sys.platform == "win32": - passenv.add("SYSTEMDRIVE") # needed for pip6 - passenv.add("SYSTEMROOT") # needed for python's crypto module - passenv.add("PATHEXT") # needed for discovering executables - passenv.add("TEMP") - passenv.add("TMP") - else: - passenv.add("TMPDIR") - for spec in value: - for name in os.environ: - if fnmatchcase(name.upper(), spec.upper()): - passenv.add(name) - 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.") - - parser.add_testenv_attribute( - 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.") - - parser.add_testenv_attribute( - name="platform", type="string", default=".*", - help="regular expression which must match against ``sys.platform``. " - "otherwise testenv will be skipped.") - - def sitepackages(testenv_config, value): - return testenv_config.config.option.sitepackages or value - - parser.add_testenv_attribute( - 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.") - - 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. ") - - 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") - - parser.add_testenv_attribute_obj(InstallcmdOption()) - - parser.add_testenv_attribute( - name="list_dependencies_command", - type="argv", - default="pip freeze", - 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.") - - parser.add_testenv_attribute( - "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") - - -class Config(object): - """ Global Tox config object. """ - def __init__(self, pluginmanager, option, interpreters): - #: dictionary containing envname to envconfig mappings - self.envconfigs = {} - self.invocationcwd = py.path.local() - self.interpreters = interpreters - self.pluginmanager = pluginmanager - #: option namespace containing all parsed command line options - self.option = option - - @property - def homedir(self): - homedir = get_homedir() - if homedir is None: - homedir = self.toxinidir # XXX good idea? - return homedir - - -class TestenvConfig: - """ Testenv Configuration object. - 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 - #: global tox config object - self.config = config - #: set of factors - self.factors = factors - self._reader = reader - - def get_envbindir(self): - """ path to directory where scripts/binaries reside. """ - if (sys.platform == "win32" - and "jython" not in self.basepython - and "pypy" not in self.basepython): - return self.envdir.join("Scripts") - else: - return self.envdir.join("bin") - - @property - def envbindir(self): - return self.get_envbindir() - - @property - def envpython(self): - """ path to python executable. """ - return self.get_envpython() - - def get_envpython(self): - """ path to python/jython executable. """ - if "jython" in str(self.basepython): - name = "jython" - else: - name = "python" - return self.envbindir.join(name) - - def get_envsitepackagesdir(self): - """ return sitepackagesdir of the virtualenv environment. - (only available during execution, not parsing) - """ - x = self.config.interpreters.get_sitepackagesdir( - info=self.python_info, - envdir=self.envdir) - return x - - @property - def python_info(self): - """ return sitepackagesdir of the virtualenv environment. """ - return self.config.interpreters.get_info(envconfig=self) - - def getsupportedinterpreter(self): - if sys.platform == "win32" and self.basepython and \ - "jython" in self.basepython: - raise tox.exception.UnsupportedInterpreter( - "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)) - if info.version_info < (2, 6): - raise tox.exception.UnsupportedInterpreter( - "python2.5 is not supported anymore, sorry") - return info.executable - - -testenvprefix = "testenv:" - - -def get_homedir(): - try: - return py.path.local._gethomedir() - except Exception: - return None - - -def make_hashseed(): - max_seed = 4294967295 - if sys.platform == 'win32': - max_seed = 1024 - return str(random.randint(1, max_seed)) - - -class parseini: - def __init__(self, config, inipath): - config.toxinipath = inipath - config.toxinidir = config.toxinipath.dirpath() - - self._cfg = py.iniconfig.IniConfig(config.toxinipath) - config._cfg = self._cfg - self.config = config - - 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']) - distshare_default = "{toxworkdir}/distshare" - elif not ctxname: - reader = SectionReader("tox", self._cfg, prefix=prefix) - distshare_default = "{homedir}/.tox/distshare" - else: - raise ValueError("invalid context") - - if config.option.hashseed is None: - hashseed = make_hashseed() - elif config.option.hashseed == 'noset': - hashseed = None - else: - hashseed = config.option.hashseed - config.hashseed = hashseed - - reader.addsubstitutions(toxinidir=config.toxinidir, - homedir=config.homedir) - # As older versions of tox may have bugs or incompatabilities that - # prevent parsing of tox.ini this must be the first thing checked. - config.minversion = reader.getstring("minversion", None) - if config.minversion: - minversion = NormalizedVersion(self.config.minversion) - toxversion = NormalizedVersion(tox.__version__) - if toxversion < minversion: - raise tox.exception.MinVersionError( - "tox version is %s, required is at least %s" % ( - 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) - - # determine indexserver dictionary - config.indexserver = {'default': IndexServerConfig('default')} - prefix = "indexserver" - for line in reader.getlist(prefix): - name, url = map(lambda x: x.strip(), line.split("=", 1)) - config.indexserver[name] = IndexServerConfig(name, url) - - override = False - if config.option.indexurl: - for urldef in config.option.indexurl: - m = re.match(r"\W*(\w+)=(\S+)", urldef) - if m is None: - url = urldef - name = "default" - else: - name, url = m.groups() - if not url: - url = None - if name != "ALL": - config.indexserver[name].url = url - else: - override = url - # let ALL override all existing entries - if override: - for name in config.indexserver: - config.indexserver[name] = IndexServerConfig(name, override) - - reader.addsubstitutions(toxworkdir=config.toxworkdir) - config.distdir = reader.getpath("distdir", "{toxworkdir}/dist") - reader.addsubstitutions(distdir=config.distdir) - config.distshare = reader.getpath("distshare", distshare_default) - reader.addsubstitutions(distshare=config.distshare) - config.sdistsrc = reader.getpath("sdistsrc", None) - config.setupdir = reader.getpath("setupdir", "{toxinidir}") - config.logdir = config.toxworkdir.join("log") - - config.envlist, all_envs = self._getenvdata(reader) - - # factors used in config or predefined - known_factors = self._list_section_factors("testenv") - known_factors.update(default_factors) - known_factors.add("python") - - # factors stated in config envlist - stated_envlist = reader.getstring("envlist", replace=False) - if stated_envlist: - for env in _split_env(stated_envlist): - known_factors.update(env.split('-')) - - # configure testenvs - for name in all_envs: - section = testenvprefix + name - 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) - - config.skipsdist = reader.getbool("skipsdist", all_develop) - - 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) - factors.update(*mapcat(_split_factor_expr, exprs)) - return factors - - def make_envconfig(self, name, section, subs, config): - factors = set(name.split('-')) - reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], - factors=factors) - vc = TestenvConfig(config=config, envname=name, factors=factors, reader=reader) - reader.addsubstitutions(**subs) - reader.addsubstitutions(envname=name) - reader.addsubstitutions(envbindir=vc.get_envbindir, - envsitepackagesdir=vc.get_envsitepackagesdir, - envpython=vc.get_envpython) - - for env_attr in config._testenv_attr: - atype = env_attr.type - if atype in ("bool", "path", "string", "dict", "dict_setenv", "argv", "argvlist"): - meth = getattr(reader, "get" + atype) - res = meth(env_attr.name, env_attr.default) - elif atype == "space-separated-list": - res = reader.getlist(env_attr.name, sep=" ") - elif atype == "line-list": - res = reader.getlist(env_attr.name, sep="\n") - else: - raise ValueError("unknown type %r" % (atype,)) - - if env_attr.postprocess: - res = env_attr.postprocess(testenv_config=vc, value=res) - setattr(vc, env_attr.name, res) - - if atype == "path": - reader.addsubstitutions(**{env_attr.name: res}) - - return vc - - 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) - - # collect section envs - all_envs = set(envlist) - set(["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) - - return envlist, all_envs - - -def _split_env(env): - """if handed a list, action="append" was used for -e """ - if not isinstance(env, list): - if '\n' in env: - env = ','.join(env.split('\n')) - env = [env] - return mapcat(_expand_envstr, env) - - -def _split_factor_expr(expr): - partial_envs = _expand_envstr(expr) - return [set(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] - - def expand(env): - tokens = re.split(r'\{([^}]+)\}', env) - parts = [token.split(',') for token in tokens] - return [''.join(variant) for variant in itertools.product(*parts)] - - return mapcat(expand, envlist) - - -def mapcat(f, seq): - return list(itertools.chain.from_iterable(map(f, seq))) - - -class DepConfig: - def __init__(self, name, indexserver=None): - self.name = name - self.indexserver = indexserver - - def __str__(self): - if self.indexserver: - if self.indexserver.name == "default": - return self.name - return ":%s:%s" % (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 - - -#: Check value matches substitution form -#: of referencing value from other section. E.g. {[base]commands} -is_section_substitution = re.compile("{\[[^{}\s]+\]\S+?}").match - - -class SectionReader: - 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._cfg = cfgparser - self.fallbacksections = fallbacksections or [] - self.factors = factors - self._subs = {} - self._subststack = [] - self._setenv = None - - def get_environ_value(self, name): - if self._setenv is None: - return os.environ.get(name) - return self._setenv.get(name) - - def addsubstitutions(self, _posargs=None, **kw): - self._subs.update(kw) - if _posargs: - self.posargs = _posargs - - def getpath(self, name, defaultpath): - toxinidir = self._subs['toxinidir'] - path = self.getstring(name, defaultpath) - if path is not None: - return toxinidir.join(path, abs=True) - - def getlist(self, name, sep="\n"): - s = self.getstring(name, None) - if s is None: - return [] - return [x.strip() for x in s.split(sep) if x.strip()] - - def getdict(self, name, default=None, sep="\n"): - value = self.getstring(name, None) - return self._getdict(value, default=default, sep=sep) - - def getdict_setenv(self, name, default=None, sep="\n"): - value = self.getstring(name, None, replace=True, crossonly=True) - definitions = self._getdict(value, default=default, sep=sep) - self._setenv = SetenvDict(definitions, reader=self) - return self._setenv - - def _getdict(self, value, default, sep): - if value is None: - return default or {} - - d = {} - for line in value.split(sep): - if line.strip(): - name, rest = line.split('=', 1) - d[name.strip()] = rest.strip() - - return d - - def getbool(self, name, default=None): - s = self.getstring(name, default) - if not s: - s = default - if s is None: - raise KeyError("no config value [%s] %s found" % ( - self.section_name, name)) - - if not isinstance(s, bool): - if s.lower() == "true": - s = True - elif s.lower() == "false": - s = False - else: - raise tox.exception.ConfigError( - "boolean value %r needs to be 'True' or 'False'") - return s - - def getargvlist(self, name, default=""): - s = self.getstring(name, default, replace=False) - return _ArgvlistReader.getargvlist(self, s) - - def getargv(self, name, default=""): - return self.getargvlist(name, default)[0] - - def getstring(self, name, default=None, replace=True, crossonly=False): - x = None - for s in [self.section_name] + self.fallbacksections: - try: - x = self._cfg[s][name] - break - except KeyError: - continue - - if x is None: - x = default - else: - x = self._apply_factors(x) - - if replace and x and hasattr(x, 'replace'): - x = self._replace(x, name=name, crossonly=crossonly) - # print "getstring", self.section_name, name, "returned", repr(x) - return x - - def _apply_factors(self, s): - def factor_line(line): - m = re.search(r'^([\w{}\.,-]+)\:\s+(.+)', line) - if not m: - return line - - expr, line = m.groups() - if any(fs <= self.factors for fs in _split_factor_expr(expr)): - return line - - lines = s.strip().splitlines() - return '\n'.join(filter(None, map(factor_line, lines))) - - def _replace(self, value, name=None, section_name=None, crossonly=False): - if '{' not in value: - return value - - section_name = section_name if section_name else self.section_name - self._subststack.append((section_name, name)) - try: - return Replacer(self, crossonly=crossonly).do_replace(value) - finally: - assert self._subststack.pop() == (section_name, name) - - -class Replacer: - RE_ITEM_REF = re.compile( - r''' - (?<!\\)[{] - (?:(?P<sub_type>[^[:{}]+):)? # optional sub_type for special rules - (?P<substitution_value>[^,{}]*) # substitution key - [}] - ''', re.VERBOSE) - - def __init__(self, reader, crossonly=False): - self.reader = reader - self.crossonly = crossonly - - def do_replace(self, x): - return self.RE_ITEM_REF.sub(self._replace_match, x) - - def _replace_match(self, match): - g = match.groupdict() - sub_value = g['substitution_value'] - if self.crossonly: - if sub_value.startswith("["): - return self._substitute_from_other_section(sub_value) - # in crossonly we return all other hits verbatim - start, end = match.span() - return match.string[start:end] - - # 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 - - try: - sub_type = g['sub_type'] - except KeyError: - raise tox.exception.ConfigError( - "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) - return self._replace_substitution(match) - - def _replace_env(self, match): - match_value = match.group('substitution_value') - if not match_value: - raise tox.exception.ConfigError( - 'env: requires an environment variable name') - - default = None - envkey_split = match_value.split(':', 1) - - if len(envkey_split) is 2: - envkey, default = envkey_split - else: - envkey = match_value - - envvalue = self.reader.get_environ_value(envkey) - if envvalue is None: - if default is None: - raise tox.exception.ConfigError( - "substitution env:%r: unknown environment variable %r " - " or recursive definition." % - (envkey, envkey)) - return default - return envvalue - - def _substitute_from_other_section(self, key): - if key.startswith("[") and "]" in key: - i = key.find("]") - section, item = key[1:i], key[i + 1:] - 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)) - x = str(cfg[section][item]) - return self.reader._replace(x, name=item, section_name=section, - crossonly=self.crossonly) - - raise tox.exception.ConfigError( - "substitution key %r not found" % key) - - def _replace_substitution(self, match): - 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) - if py.builtin.callable(val): - val = val() - return str(val) - - -class _ArgvlistReader: - @classmethod - def getargvlist(cls, reader, value): - """Parse ``commands`` argvlist multiline string. - - :param str name: Key name in a section. - :param str value: Content stored by key. - - :rtype: list[list[str]] - :raise :class:`tox.exception.ConfigError`: - line-continuation ends nowhere while resolving for specified section - """ - commands = [] - current_command = "" - for line in value.splitlines(): - line = line.rstrip() - if not line: - continue - if line.endswith("\\"): - current_command += " " + line[:-1] - continue - current_command += line - - if is_section_substitution(current_command): - replaced = reader._replace(current_command, crossonly=True) - commands.extend(cls.getargvlist(reader, replaced)) - else: - commands.append(cls.processcommand(reader, current_command)) - current_command = "" - else: - if current_command: - raise tox.exception.ConfigError( - "line-continuation ends nowhere while resolving for [%s] %s" % - (reader.section_name, "commands")) - return commands - - @classmethod - def processcommand(cls, reader, command): - posargs = getattr(reader, "posargs", None) - - # Iterate through each word of the command substituting as - # appropriate to construct the new command string. This - # string is then broken up into exec argv components using - # shlex. - newcommand = "" - for word in CommandParser(command).words(): - if word == "{posargs}" or word == "[]": - if posargs: - newcommand += " ".join(posargs) - continue - elif word.startswith("{posargs:") and word.endswith("}"): - if posargs: - newcommand += " ".join(posargs) - continue - else: - word = word[9:-1] - new_arg = "" - new_word = reader._replace(word) - new_word = reader._replace(new_word) - new_word = new_word.replace('\\{', '{').replace('\\}', '}') - new_arg += new_word - newcommand += new_arg - - # Construct shlex object that will not escape any values, - # use all values as is in argv. - shlexer = shlex.shlex(newcommand, posix=True) - shlexer.whitespace_split = True - shlexer.escape = '' - argv = list(shlexer) - return argv - - -class CommandParser(object): - - class State(object): - def __init__(self): - self.word = '' - self.depth = 0 - self.yield_words = [] - - def __init__(self, command): - self.command = command - - 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() == '')) - - def yield_this_word(): - yieldword = ps.word - ps.word = '' - if yieldword: - ps.yield_words.append(yieldword) - - def yield_if_word_ended(): - if word_has_ended(): - yield_this_word() - - def accumulate(): - ps.word += cur_char - - def push_substitution(): - ps.depth += 1 - - def pop_substitution(): - ps.depth -= 1 - - for cur_char in self.command: - if cur_char in string.whitespace: - if ps.depth == 0: - yield_if_word_ended() - accumulate() - elif cur_char == '{': - yield_if_word_ended() - accumulate() - push_substitution() - elif cur_char == '}': - accumulate() - pop_substitution() - else: - yield_if_word_ended() - accumulate() - - if ps.word.strip(): - yield_this_word() - return ps.yield_words - - -def getcontextname(): - if any(env in os.environ for env in ['JENKINS_URL', 'HUDSON_URL']): - return 'jenkins' - return None diff --git a/tox/hookspecs.py b/tox/hookspecs.py deleted file mode 100644 index 22e231d..0000000 --- a/tox/hookspecs.py +++ /dev/null @@ -1,59 +0,0 @@ -""" Hook specifications for tox. - -""" - -from pluggy import HookspecMarker, HookimplMarker - -hookspec = HookspecMarker("tox") -hookimpl = HookimplMarker("tox") - - -@hookspec -def tox_addoption(parser): - """ add command line options to the argparse-style parser object.""" - - -@hookspec -def tox_configure(config): - """ called after command line options have been parsed and the ini-file has - been read. Please be aware that the config object layout may change as its - API was not designed yet wrt to providing stability (it was an internal - thing purely before tox-2.0). """ - - -@hookspec(firstresult=True) -def tox_get_python_executable(envconfig): - """ return a python executable for the given python base name. - The first plugin/hook which returns an executable path will determine it. - - ``envconfig`` is the testenv configuration which contains - per-testenv configuration, notably the ``.envname`` and ``.basepython`` - setting. - """ - - -@hookspec -def tox_testenv_create(venv, action): - """ [experimental] perform creation action for this venv. - """ - - -@hookspec -def tox_testenv_install_deps(venv, action): - """ [experimental] perform install dependencies action for this venv. """ - - -@hookspec -def tox_runtest_pre(venv): - """ [experimental] perform arbitrary action before running tests for this venv. - - This could be used to indicate that tests for a given venv have started, for instance. - """ - - -@hookspec -def tox_runtest_post(venv): - """ [experimental] perform arbitrary action after running tests for this venv. - - This could be used to have per-venv test reporting of pass/fail status. - """ diff --git a/tox/interpreters.py b/tox/interpreters.py deleted file mode 100644 index e049f1d..0000000 --- a/tox/interpreters.py +++ /dev/null @@ -1,184 +0,0 @@ -import sys -import py -import re -import inspect -from tox import hookimpl - - -class Interpreters: - def __init__(self, hook): - self.name2executable = {} - self.executable2info = {} - self.hook = hook - - def get_executable(self, envconfig): - """ return path object to the executable for the given - name (e.g. python2.6, python2.7, python etc.) - if name is already an existing path, return name. - If an interpreter cannot be found, return None. - """ - try: - return self.name2executable[envconfig.envname] - except KeyError: - exe = self.hook.tox_get_python_executable(envconfig=envconfig) - self.name2executable[envconfig.envname] = exe - return exe - - def get_info(self, envconfig): - executable = self.get_executable(envconfig) - name = envconfig.basepython - if not executable: - return NoInterpreterInfo(name=name) - try: - return self.executable2info[executable] - except KeyError: - info = run_and_get_interpreter_info(name, executable) - self.executable2info[executable] = info - return info - - def get_sitepackagesdir(self, info, envdir): - if not info.executable: - return "" - envdir = str(envdir) - try: - res = exec_on_interpreter(info.executable, - [inspect.getsource(sitepackagesdir), - "print(sitepackagesdir(%r))" % envdir]) - except ExecFailed: - val = sys.exc_info()[1] - print("execution failed: %s -- %s" % (val.out, val.err)) - return "" - else: - return res["dir"] - - -def run_and_get_interpreter_info(name, executable): - assert executable - try: - result = exec_on_interpreter(executable, - [inspect.getsource(pyinfo), "print(pyinfo())"]) - except ExecFailed: - val = sys.exc_info()[1] - return NoInterpreterInfo(name, executable=val.executable, - out=val.out, err=val.err) - else: - return InterpreterInfo(name, executable, **result) - - -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")) - out, err = popen.communicate() - if popen.returncode: - raise ExecFailed(executable, source, out, err) - try: - result = eval(out.strip()) - except Exception: - raise ExecFailed(executable, source, out, - "could not decode %r" % out) - return result - - -class ExecFailed(Exception): - def __init__(self, executable, source, out, err): - self.executable = executable - self.source = source - self.out = out - self.err = err - - -class InterpreterInfo: - runnable = True - - def __init__(self, name, executable, version_info, sysplatform): - assert executable and version_info - self.name = name - self.executable = executable - self.version_info = version_info - self.sysplatform = sysplatform - - def __str__(self): - return "<executable at %s, version_info %s>" % ( - self.executable, self.version_info) - - -class NoInterpreterInfo: - runnable = False - - def __init__(self, name, executable=None, - out=None, err="not found"): - self.name = name - self.executable = executable - self.version_info = None - self.out = out - self.err = err - - def __str__(self): - if self.executable: - return "<executable at %s, not runnable>" - else: - return "<executable not found for: %s>" % self.name - -if sys.platform != "win32": - @hookimpl - def tox_get_python_executable(envconfig): - return py.path.local.sysfind(envconfig.basepython) - -else: - @hookimpl - def tox_get_python_executable(envconfig): - name = envconfig.basepython - p = py.path.local.sysfind(name) - if p: - return p - actual = None - # Is this a standard PythonX.Y name? - 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() - if not actual: - actual = win32map.get(name, None) - if actual: - actual = py.path.local(actual) - if actual.check(): - return actual - # The standard executables can be found as a last resort via the - # Python launcher py.exe - if m: - return locate_via_py(*m.groups()) - - # Exceptions to the usual windows mapping - win32map = { - 'python': sys.executable, - 'jython': "c:\jython2.5.1\jython.bat", - } - - def locate_via_py(v_maj, v_min): - ver = "-%s.%s" % (v_maj, v_min) - script = "import sys; print(sys.executable)" - py_exe = py.path.local.sysfind('py') - if py_exe: - try: - exe = py_exe.sysexec(ver, '-c', script).strip() - except py.process.cmdexec.Error: - exe = None - if exe: - exe = py.path.local(exe) - if exe.check(): - return exe - - -def pyinfo(): - import sys - return dict(version_info=tuple(sys.version_info), - sysplatform=sys.platform) - - -def sitepackagesdir(envdir): - from distutils.sysconfig import get_python_lib - return dict(dir=get_python_lib(prefix=envdir)) diff --git a/tox/result.py b/tox/result.py deleted file mode 100644 index bad8cc3..0000000 --- a/tox/result.py +++ /dev/null @@ -1,81 +0,0 @@ -import sys -import py -from tox import __version__ as toxver -import json - - -class ResultLog: - - def __init__(self, dict=None): - if dict is None: - dict = {} - self.dict = dict - self.dict.update({"reportversion": "1", "toxversion": toxver}) - self.dict["platform"] = sys.platform - self.dict["host"] = py.std.socket.getfqdn() - - def set_header(self, installpkg): - """ - :param py.path.local installpkg: Path ot the package. - """ - self.dict["installpkg"] = dict( - md5=installpkg.computehash("md5"), - sha256=installpkg.computehash("sha256"), - basename=installpkg.basename, - ) - - def get_envlog(self, name): - testenvs = self.dict.setdefault("testenvs", {}) - d = testenvs.setdefault(name, {}) - return EnvLog(self, name, d) - - def dumps_json(self): - return json.dumps(self.dict, indent=2) - - @classmethod - def loads_json(cls, data): - return cls(json.loads(data)) - - -class EnvLog: - def __init__(self, reportlog, name, dict): - self.reportlog = reportlog - self.name = name - self.dict = 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)") - lines = out.splitlines() - executable = lines.pop(0) - version_info = eval(lines.pop(0)) - version = "\n".join(lines) - self.dict["python"] = dict( - executable=executable, - version_info=version_info, - version=version) - - def get_commandlog(self, name): - l = self.dict.setdefault(name, []) - return CommandLog(self, l) - - def set_installed(self, packages): - self.dict["installed_packages"] = packages - - -class CommandLog: - def __init__(self, envlog, list): - self.envlog = envlog - self.list = list - - def add_command(self, argv, output, retcode): - d = {} - self.list.append(d) - d["command"] = argv - d["output"] = output - d["retcode"] = str(retcode) - return d diff --git a/tox/session.py b/tox/session.py deleted file mode 100644 index e29696e..0000000 --- a/tox/session.py +++ /dev/null @@ -1,682 +0,0 @@ -""" -Automatically package and test a Python project against configurable -Python2 and Python3 based virtual environments. Environments are -setup by using virtualenv. Configuration is generally done through an -INI-style "tox.ini" file. -""" -from __future__ import with_statement - -import tox -import py -import os -import sys -import subprocess -from tox._verlib import NormalizedVersion, IrrationalVersionError -from tox.venv import VirtualEnv -from tox.config import parseconfig -from tox.result import ResultLog -from subprocess import STDOUT - - -def now(): - return py.std.time.time() - - -def prepare(args): - config = parseconfig(args) - if config.option.help: - show_help(config) - raise SystemExit(0) - elif config.option.helpini: - show_help_ini(config) - raise SystemExit(0) - return config - - -def main(args=None): - try: - config = prepare(args) - retcode = Session(config).runcommand() - raise SystemExit(retcode) - except KeyboardInterrupt: - raise SystemExit(2) - except tox.exception.MinVersionError as e: - r = Reporter(None) - r.error(e.message) - raise SystemExit(1) - - -def show_help(config): - tw = py.io.TerminalWriter() - 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") - - -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(env_attr.help) - tw.line() - - -class Action(object): - def __init__(self, session, venv, msg, args): - self.venv = venv - self.msg = msg - self.activity = msg.split(" ", 1)[0] - self.session = session - self.report = session.report - self.args = args - self.id = venv and venv.envconfig.envname or "tox" - self._popenlist = [] - if self.venv: - self.venvname = self.venv.name - else: - self.venvname = "GLOB" - if msg == "runtests": - cat = "test" - else: - cat = "setup" - envlog = session.resultlog.get_envlog(self.venvname) - self.commandlog = envlog.get_commandlog(cat) - - def __enter__(self): - self.report.logaction_start(self) - - def __exit__(self, *args): - self.report.logaction_finish(self) - - def setactivity(self, name, msg): - self.activity = name - self.report.verbosity0("%s %s: %s" % (self.venvname, name, msg), bold=True) - - def info(self, name, msg): - self.report.verbosity1("%s %s: %s" % (self.venvname, name, msg), bold=True) - - def _initlogpath(self, actionid): - if self.venv: - logdir = self.venv.envconfig.envlogdir - else: - logdir = self.session.config.logdir - try: - l = logdir.listdir("%s-*" % actionid) - except py.error.ENOENT: - logdir.ensure(dir=1) - l = [] - num = len(l) - path = logdir.join("%s-%s.log" % (actionid, num)) - f = path.open('w') - f.flush() - return f - - def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore_ret=False): - stdout = outpath = None - resultjson = self.session.config.option.resultjson - if resultjson or redirect: - fout = self._initlogpath(self.id) - fout.write("actionid: %s\nmsg: %s\ncmdargs: %r\nenv: %s\n\n" % ( - self.id, self.msg, args, env)) - fout.flush() - self.popen_outpath = outpath = py.path.local(fout.name) - fin = outpath.open() - fin.read() # read the header, so it won't be written to stdout - stdout = fout - elif returnout: - stdout = subprocess.PIPE - if cwd is None: - # XXX cwd = self.session.config.cwd - cwd = py.path.local() - try: - popen = self._popen(args, cwd, env=env, - stdout=stdout, stderr=STDOUT) - except OSError as e: - self.report.error("invocation failed (errno %d), args: %s, cwd: %s" % - (e.errno, args, cwd)) - raise - popen.outpath = outpath - popen.args = [str(x) for x in args] - popen.cwd = cwd - popen.action = self - self._popenlist.append(popen) - try: - self.report.logpopen(popen, env=env) - try: - if resultjson and not redirect: - assert popen.stderr is None # prevent deadlock - out = None - last_time = now() - while 1: - fin_pos = fin.tell() - # we have to read one byte at a time, otherwise there - # might be no output for a long time with slow tests - data = fin.read(1) - if data: - sys.stdout.write(data) - if '\n' in data or (now() - 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 - sys.stdout.flush() - last_time = now() - elif popen.poll() is not None: - if popen.stdout is not None: - popen.stdout.close() - break - else: - py.std.time.sleep(0.1) - fin.seek(fin_pos) - fin.close() - else: - out, err = popen.communicate() - except KeyboardInterrupt: - self.report.keyboard_interrupt() - popen.wait() - raise KeyboardInterrupt() - ret = popen.wait() - finally: - self._popenlist.remove(popen) - 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)) - 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) - else: - raise tox.exception.InvocationError("%r" % (invoked, ), ret) - if not out and outpath: - out = outpath.read() - if hasattr(self, "commandlog"): - self.commandlog.add_command(popen.args, out, ret) - return out - - def _rewriteargs(self, cwd, args): - newargs = [] - for arg in args: - if sys.platform != "win32" and isinstance(arg, py.path.local): - arg = cwd.bestrelpath(arg) - newargs.append(str(arg)) - - # subprocess does not always take kindly to .py scripts - # so adding the interpreter here. - if sys.platform == "win32": - ext = os.path.splitext(str(newargs[0]))[1].lower() - if ext == '.py' and self.venv: - newargs = [str(self.venv.envconfig.envpython)] + newargs - - return newargs - - def _popen(self, args, cwd, stdout, stderr, env=None): - args = self._rewriteargs(cwd, args) - if env is None: - env = os.environ.copy() - return self.session.popen(args, shell=False, cwd=str(cwd), - universal_newlines=True, - stdout=stdout, stderr=stderr, env=env) - - -class Reporter(object): - actionchar = "-" - - def __init__(self, session): - self.tw = py.io.TerminalWriter() - self.session = session - self._reportedlines = [] - # self.cumulated_time = 0.0 - - def _get_verbosity(self): - if self.session: - return self.session.config.option.verbosity - else: - return 2 - - 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,)) - else: - self.verbosity1(" %s$ %s " % (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) - assert not hasattr(action, "_starttime") - action._starttime = now() - - def logaction_finish(self, action): - duration = now() - action._starttime - # self.cumulated_time += duration - self.verbosity2("%s finish: %s after %.2f seconds" % ( - action.venvname, action.msg, duration), bold=True) - delattr(action, '_starttime') - - def startsummary(self): - self.tw.sep("_", "summary") - - def info(self, msg): - if self._get_verbosity() >= 2: - self.logline(msg) - - def using(self, msg): - if self._get_verbosity() >= 1: - self.logline("using %s" % (msg,), bold=True) - - def keyboard_interrupt(self): - self.error("KEYBOARDINTERRUPT") - -# def venv_installproject(self, venv, pkg): -# self.logline("installing to %s: %s" % (venv.envconfig.envname, pkg)) - - def keyvalue(self, name, value): - if name.endswith(":"): - name += " " - self.tw.write(name, bold=True) - self.tw.write(value) - self.tw.line() - - def line(self, msg, **opts): - self.logline(msg, **opts) - - def good(self, msg): - self.logline(msg, green=True) - - def warning(self, msg): - self.logline("WARNING:" + msg, red=True) - - def error(self, msg): - self.logline("ERROR: " + msg, red=True) - - def skip(self, msg): - self.logline("SKIPPED:" + msg, yellow=True) - - def logline(self, msg, **opts): - self._reportedlines.append(msg) - self.tw.line("%s" % msg, **opts) - - def verbosity0(self, msg, **opts): - if self._get_verbosity() >= 0: - self.logline("%s" % msg, **opts) - - def verbosity1(self, msg, **opts): - if self._get_verbosity() >= 1: - self.logline("%s" % msg, **opts) - - def verbosity2(self, msg, **opts): - if self._get_verbosity() >= 2: - self.logline("%s" % msg, **opts) - - # def log(self, msg): - # py.builtin.print_(msg, file=sys.stderr) - - -class Session: - """ (unstable API). the session object that ties - together configuration, reporting, venv creation, testing. """ - - def __init__(self, config, popen=subprocess.Popen, Report=Reporter): - self.config = config - self.popen = popen - self.resultlog = ResultLog() - self.report = Report(self) - self.make_emptydir(config.logdir) - config.logdir.ensure(dir=1) - # self.report.using("logdir %s" %(self.config.logdir,)) - self.report.using("tox.ini: %s" % (self.config.toxinipath,)) - self._spec2pkg = {} - self._name2venv = {} - try: - self.venvlist = [ - self.getvenv(x) - for x in self.config.envlist - ] - except LookupError: - raise SystemExit(1) - self._actions = [] - - @property - def hook(self): - return self.config.pluginmanager.hook - - def _makevenv(self, name): - envconfig = self.config.envconfigs.get(name, None) - if envconfig is None: - self.report.error("unknown environment %r" % name) - raise LookupError(name) - venv = VirtualEnv(envconfig=envconfig, session=self) - self._name2venv[name] = venv - return venv - - def getvenv(self, name): - """ return a VirtualEnv controler object for the 'name' env. """ - try: - return self._name2venv[name] - except KeyError: - return self._makevenv(name) - - def newaction(self, venv, msg, *args): - action = Action(self, venv, msg, args) - self._actions.append(action) - return action - - def runcommand(self): - self.report.using("tox-%s from %s" % (tox.__version__, tox.__file__)) - if self.config.option.showconfig: - self.showconfig() - elif self.config.option.listenvs: - self.showenvs() - else: - return self.subcommand_test() - - 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,)) - raise SystemExit(1) - target = destdir.join(relpath) - target.dirpath().ensure(dir=1) - src.copy(target) - - def _makesdist(self): - setup = self.config.setupdir.join("setup.py") - if not setup.check(): - raise tox.exception.MissingFile(setup) - 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) - try: - return self.config.distdir.listdir()[0] - except py.error.ENOENT: - # check if empty or comment only - data = [] - with open(str(setup)) as fp: - for line in fp: - if line and line[0] == '#': - continue - data.append(line) - 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' - ) - raise SystemExit(1) - - def make_emptydir(self, path): - if path.check(): - self.report.info(" removing %s" % path) - py.std.shutil.rmtree(str(path), ignore_errors=True) - path.ensure(dir=1) - - def setupenv(self, venv): - if not venv.matching_platform(): - venv.status = "platform mismatch" - return # we simply omit non-matching platforms - action = self.newaction(venv, "getenv", venv.envconfig.envdir) - with action: - venv.status = 0 - envlog = self.resultlog.get_envlog(venv.name) - try: - status = venv.update(action=action) - except tox.exception.InvocationError: - status = sys.exc_info()[1] - if status: - commandlog = envlog.get_commandlog("setup") - commandlog.add_command(["setup virtualenv"], str(status), 1) - venv.status = status - self.report.error(str(status)) - return False - commandpath = venv.getcommandpath("python") - envlog.set_python_info(commandpath) - return True - - def finishvenv(self, venv): - action = self.newaction(venv, "finishvenv") - with action: - venv.finish() - return True - - def developpkg(self, venv, setupdir): - action = self.newaction(venv, "developpkg", setupdir) - with action: - try: - venv.developpkg(setupdir, action) - return True - except tox.exception.InvocationError: - venv.status = sys.exc_info()[1] - return False - - def installpkg(self, venv, path): - """Install package in the specified virtual environment. - - :param :class:`tox.config.VenvConfig`: Destination environment - :param str path: Path to the distribution package. - :return: True if package installed otherwise False. - :rtype: bool - """ - self.resultlog.set_header(installpkg=py.path.local(path)) - action = self.newaction(venv, "installpkg", path) - with action: - try: - venv.installpkg(path, action) - return True - except tox.exception.InvocationError: - venv.status = sys.exc_info()[1] - return False - - 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): - 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)) - 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) - return - sdistfile = self.config.distshare.join(path.basename) - if sdistfile != path: - self.report.info("copying new sdistfile to %r" % - str(sdistfile)) - try: - sdistfile.dirpath().ensure(dir=1) - except py.error.Error: - self.report.warning("could not copy distfile to %s" % - sdistfile.dirpath()) - else: - path.copy(sdistfile) - return path - - def subcommand_test(self): - if self.config.skipsdist: - self.report.info("skipping sdist step") - path = None - else: - path = self.get_installpkg_path() - if not path: - return 2 - if self.config.option.sdistonly: - return - for venv in self.venvlist: - if self.setupenv(venv): - if venv.envconfig.usedevelop: - self.developpkg(venv, self.config.setupdir) - elif self.config.skipsdist or venv.envconfig.skip_install: - self.finishvenv(venv) - else: - self.installpkg(venv, path) - - # write out version dependency information - action = self.newaction(venv, "envreport") - with action: - args = venv.envconfig.list_dependencies_command - output = venv._pcall(args, - cwd=self.config.toxinidir, - action=action) - # the output contains a mime-header, skip it - output = output.split("\n\n")[-1] - packages = output.strip().split("\n") - action.setactivity("installed", ",".join(packages)) - envlog = self.resultlog.get_envlog(venv.name) - envlog.set_installed(packages) - - self.runtestenv(venv) - retcode = self._summary() - return retcode - - def runtestenv(self, venv, redirect=False): - if not self.config.option.notest: - if venv.status: - return - self.hook.tox_runtest_pre(venv=venv) - venv.test(redirect=redirect) - self.hook.tox_runtest_post(venv=venv) - else: - venv.status = "skipped tests" - - def _summary(self): - self.report.startsummary() - retcode = 0 - for venv in self.venvlist: - status = venv.status - if isinstance(status, tox.exception.InterpreterNotFound): - msg = " %s: %s" % (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)) - self.report.skip(msg) - elif status and status == "ignored failed command": - msg = " %s: %s" % (venv.envconfig.envname, str(status)) - self.report.good(msg) - elif status and status != "skipped tests": - msg = " %s: %s" % (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)) - if not retcode: - self.report.good(" congratulations :)") - - path = self.config.option.resultjson - if path: - path = py.path.local(path) - path.write(self.resultlog.dumps_json()) - self.report.line("wrote json report at: %s" % path) - return retcode - - def showconfig(self): - self.info_versions() - self.report.keyvalue("config-file:", self.config.option.configfile) - self.report.keyvalue("toxinipath: ", self.config.toxinipath) - self.report.keyvalue("toxinidir: ", self.config.toxinidir) - self.report.keyvalue("toxworkdir: ", self.config.toxworkdir) - self.report.keyvalue("setupdir: ", self.config.setupdir) - self.report.keyvalue("distshare: ", self.config.distshare) - 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) - for attr in self.config._parser._testenv_attr: - self.report.line(" %-15s = %s" - % (attr.name, getattr(envconfig, attr.name))) - - def showenvs(self): - for env in self.config.envlist: - self.report.line("%s" % env) - - def info_versions(self): - versions = ['tox-%s' % tox.__version__] - try: - version = py.process.cmdexec("virtualenv --version") - except py.process.cmdexec.Error: - versions.append("virtualenv-1.9.1 (vendored)") - else: - versions.append("virtualenv-%s" % version.strip()) - self.report.keyvalue("tool-versions:", " ".join(versions)) - - def _resolve_pkg(self, pkgspec): - try: - return self._spec2pkg[pkgspec] - except KeyError: - self._spec2pkg[pkgspec] = x = self._resolvepkg(pkgspec) - return x - - def _resolvepkg(self, pkgspec): - if not os.path.isabs(str(pkgspec)): - return pkgspec - p = py.path.local(pkgspec) - if p.check(): - return p - if not p.dirpath().check(dir=1): - raise tox.exception.MissingDirectory(p.dirpath()) - self.report.info("determining %s" % p) - candidates = p.dirpath().listdir(p.basename) - if len(candidates) == 0: - raise tox.exception.MissingDependency(pkgspec) - if len(candidates) > 1: - items = [] - for x in candidates: - ver = getversion(x.basename) - if ver is not None: - items.append((ver, x)) - else: - self.report.warning("could not determine version of: %s" % - str(x)) - items.sort() - if not items: - raise tox.exception.MissingDependency(pkgspec) - return items[-1][1] - else: - return candidates[0] - - -_rex_getversion = py.std.re.compile("[\w_\-\+\.]+-(.*)(\.zip|\.tar.gz)") - - -def getversion(basename): - m = _rex_getversion.match(basename) - if m is None: - return None - version = m.group(1) - try: - return NormalizedVersion(version) - except IrrationalVersionError: - return None diff --git a/tox/venv.py b/tox/venv.py deleted file mode 100644 index 0461fc7..0000000 --- a/tox/venv.py +++ /dev/null @@ -1,416 +0,0 @@ -from __future__ import with_statement -import os -import sys -import re -import codecs -import py -import tox -from .config import DepConfig, hookimpl - - -class CreationConfig: - def __init__(self, md5, python, version, sitepackages, - usedevelop, deps): - self.md5 = md5 - self.python = python - self.version = version - self.sitepackages = sitepackages - self.usedevelop = usedevelop - self.deps = deps - - def writeconfig(self, path): - lines = ["%s %s" % (self.md5, self.python)] - lines.append("%s %d %d" % (self.version, self.sitepackages, self.usedevelop)) - for dep in self.deps: - lines.append("%s %s" % dep) - path.ensure() - path.write("\n".join(lines)) - - @classmethod - def readconfig(cls, path): - try: - lines = path.readlines(cr=0) - value = lines.pop(0).split(None, 1) - md5, python = value - version, sitepackages, usedevelop = lines.pop(0).split(None, 3) - sitepackages = bool(int(sitepackages)) - usedevelop = bool(int(usedevelop)) - deps = [] - for line in lines: - md5, depstring = line.split(None, 1) - deps.append((md5, depstring)) - return CreationConfig(md5, python, version, sitepackages, usedevelop, deps) - except Exception: - 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.deps == other.deps) - - -class VirtualEnv(object): - def __init__(self, envconfig=None, session=None): - self.envconfig = envconfig - self.session = session - - @property - def hook(self): - return self.envconfig.config.pluginmanager.hook - - @property - def path(self): - """ Path to environment base dir. """ - return self.envconfig.envdir - - @property - def path_config(self): - return self.path.join(".tox-config1") - - @property - def name(self): - """ test environment name. """ - return self.envconfig.envname - - def __repr__(self): - return "<VirtualEnv at %r>" % (self.path) - - def getcommandpath(self, name, venv=True, cwd=None): - """ Return absolute path (str or localpath) for specified command name. - - If it's a local path we will rewrite it as as a relative path. - - If venv is True we will check if the command is coming from the venv - or is whitelisted to come from external. - """ - name = str(name) - if os.path.isabs(name): - return name - if os.path.split(name)[0] == ".": - path = cwd.join(name) - if path.check(): - return str(path) - - if venv: - path = self._venv_lookup_and_check_external_whitelist(name) - else: - path = self._normal_lookup(name) - - if path is None: - raise tox.exception.InvocationError( - "could not find executable %r" % (name,)) - - return str(path) # will not be rewritten for reporting - - def _venv_lookup_and_check_external_whitelist(self, name): - path = self._venv_lookup(name) - if path is None: - path = self._normal_lookup(name) - if path is not None: - self._check_external_allowed_and_warn(path) - return path - - def _venv_lookup(self, name): - return py.path.local.sysfind(name, paths=[self.envconfig.envbindir]) - - def _normal_lookup(self, name): - return py.path.local.sysfind(name) - - 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" - "Maybe you forgot to specify a dependency? " - "See also the whitelist_externals envconfig setting." % ( - path, self.envconfig.envdir)) - - def is_allowed_external(self, p): - tryadd = [""] - if sys.platform == "win32": - 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: - if p.fnmatch(x + add): - return True - return False - - def _ispython3(self): - return "python3" in str(self.envconfig.basepython) - - def update(self, action): - """ return status string for updating actual venv to match configuration. - 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()): - action.info("reusing", self.envconfig.envdir) - return - if rconfig is None: - action.setactivity("create", self.envconfig.envdir) - else: - action.setactivity("recreate", self.envconfig.envdir) - try: - self.hook.tox_testenv_create(action=action, venv=self) - self.just_created = True - except tox.exception.UnsupportedInterpreter: - return sys.exc_info()[1] - except tox.exception.InterpreterNotFound: - return sys.exc_info()[1] - try: - 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) - - def _getliveconfig(self): - python = self.envconfig.python_info.executable - md5 = getdigest(python) - version = tox.__version__ - sitepackages = self.envconfig.sitepackages - develop = self.envconfig.usedevelop - deps = [] - for dep in self._getresolvedeps(): - raw_dep = dep.name - md5 = getdigest(raw_dep) - deps.append((md5, raw_dep)) - return CreationConfig(md5, python, version, - sitepackages, develop, deps) - - def _getresolvedeps(self): - l = [] - for dep in self.envconfig.deps: - if dep.indexserver is None: - res = self.session._resolve_pkg(dep.name) - if res != dep.name: - dep = dep.__class__(res) - l.append(dep) - return l - - def getsupportedinterpreter(self): - return self.envconfig.getsupportedinterpreter() - - def matching_platform(self): - return re.match(self.envconfig.platform, sys.platform) - - 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'] - output = action.popen(args, cwd=setupdir, redirect=False, - returnout=True) - name = output.strip() - egg_info = setupdir.join('.'.join((name, 'egg-info'))) - for conf_file in (setup_py, setup_cfg): - if (not egg_info.check() - or (conf_file.check() and conf_file.mtime() > egg_info.mtime())): - return True - return False - - def developpkg(self, setupdir, action): - assert action is not None - if getattr(self, 'just_created', False): - action.setactivity("develop-inst", setupdir) - self.finish() - extraopts = [] - else: - if not self._needs_reinstall(setupdir, action): - action.setactivity("develop-inst-noop", setupdir) - return - action.setactivity("develop-inst-nodeps", setupdir) - extraopts = ['--no-deps'] - self._install(['-e', setupdir], extraopts=extraopts, action=action) - - def installpkg(self, sdistpath, action): - assert action is not None - if getattr(self, 'just_created', False): - action.setactivity("inst", sdistpath) - self.finish() - extraopts = [] - else: - action.setactivity("inst-nodeps", sdistpath) - extraopts = ['-U', '--no-deps'] - self._install([sdistpath], extraopts=extraopts, action=action) - - def _installopts(self, indexserver): - l = [] - if indexserver: - l += ["-i", indexserver] - if self.envconfig.downloadcache: - self.envconfig.downloadcache.ensure(dir=1) - l.append("--download-cache=%s" % self.envconfig.downloadcache) - if self.envconfig.pip_pre: - l.append("--pre") - return l - - def run_install_command(self, packages, action, options=()): - argv = self.envconfig.install_command[:] - # use pip-script on win32 to avoid the executable locking - i = argv.index('{packages}') - argv[i:i + 1] = packages - 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__'): - os.environ.pop(x, None) - - old_stdout = sys.stdout - sys.stdout = codecs.getwriter('utf8')(sys.stdout) - self._pcall(argv, cwd=self.envconfig.config.toxinidir, action=action) - sys.stdout = old_stdout - - def _install(self, deps, extraopts=None, action=None): - if not deps: - return - d = {} - l = [] - for dep in deps: - if isinstance(dep, (str, py.path.local)): - dep = DepConfig(str(dep), None) - assert isinstance(dep, DepConfig), dep - if dep.indexserver is None: - ixserver = self.envconfig.config.indexserver['default'] - else: - ixserver = dep.indexserver - d.setdefault(ixserver, []).append(dep.name) - if ixserver not in l: - l.append(ixserver) - assert ixserver.url is None or isinstance(ixserver.url, str) - - for ixserver in l: - packages = d[ixserver] - options = self._installopts(ixserver.url) - if extraopts: - options.extend(extraopts) - self.run_install_command(packages=packages, options=options, - action=action) - - def _getenv(self, testcommand=False): - if testcommand: - # for executing tests we construct a clean environment - env = {} - for envname in self.envconfig.passenv: - if envname in os.environ: - env[envname] = os.environ[envname] - else: - # for executing non-test commands we use the full - # invocation environment - env = os.environ.copy() - - # in any case we honor per-testenv setenv configuration - env.update(self.envconfig.setenv) - - env['VIRTUAL_ENV'] = str(self.path) - return env - - def test(self, redirect=False): - action = self.session.newaction(self, "runtests") - with action: - self.status = 0 - self.session.make_emptydir(self.envconfig.envtmpdir) - cwd = self.envconfig.changedir - env = self._getenv(testcommand=True) - # Display PYTHONHASHSEED to assist with reproducibility. - action.setactivity("runtests", "PYTHONHASHSEED=%r" % env.get('PYTHONHASHSEED')) - for i, argv in enumerate(self.envconfig.commands): - # have to make strings as _pcall changes argv[0] to a local() - # happens if the same environment is invoked twice - message = "commands[%s] | %s" % (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 - if argv[0].startswith("-"): - ignore_ret = True - if argv[0] == "-": - del argv[0] - else: - argv[0] = argv[0].lstrip("-") - else: - ignore_ret = False - - try: - 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),)) - self.status = "ignored failed command" - continue # keep processing commands - - self.session.report.error(str(err)) - self.status = "commands failed" - if not self.envconfig.ignore_errors: - break # Don't process remaining commands - except KeyboardInterrupt: - self.status = "keyboardinterrupt" - self.session.report.error(self.status) - raise - - def _pcall(self, args, cwd, venv=True, testcommand=False, - action=None, redirect=True, ignore_ret=False): - for name in ("VIRTUALENV_PYTHON", "PYTHONDONTWRITEBYTECODE"): - os.environ.pop(name, None) - - cwd.ensure(dir=1) - args[0] = self.getcommandpath(args[0], venv, cwd) - 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) - - -def getdigest(path): - path = py.path.local(path) - if not path.check(file=1): - return "0" * 32 - return path.computehash() - - -@hookimpl -def tox_testenv_create(venv, action): - # if self.getcommandpath("activate").dirpath().check(): - # return - config_interpreter = venv.getsupportedinterpreter() - args = [sys.executable, '-m', 'virtualenv'] - if venv.envconfig.sitepackages: - args.append('--system-site-packages') - # add interpreter explicitly, to prevent using - # default (virtualenv.ini) - args.extend(['--python', str(config_interpreter)]) - # if sys.platform == "win32": - # f, path, _ = py.std.imp.find_module("virtualenv") - # f.close() - # args[:1] = [str(config_interpreter), str(path)] - # else: - venv.session.make_emptydir(venv.path) - basepath = venv.path.dirpath() - basepath.ensure(dir=1) - args.append(venv.path.basename) - venv._pcall(args, venv=False, action=action, cwd=basepath) - - -@hookimpl -def tox_testenv_install_deps(venv, action): - deps = venv._getresolvedeps() - if deps: - depinfo = ", ".join(map(str, deps)) - action.setactivity("installdeps", "%s" % depinfo) - venv._install(deps, action=action) |