From 2a4a7cac656306e1530bdcc6f45bdd3743c08d36 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 20 Nov 2012 12:27:25 +0100 Subject: make tox's fixtures importable from other projects --- tests/conftest.py | 290 --------------------------------------------------- tox/_pytestplugin.py | 284 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 290 deletions(-) delete mode 100644 tests/conftest.py create mode 100644 tox/_pytestplugin.py diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 2f33022..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,290 +0,0 @@ -import py -import tox -import os -import sys -from py.builtin import print_ -from fnmatch import fnmatch -import time -from tox._config import parseconfig -from tox._venv import VirtualEnv -from tox._cmdline import Action - -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_report_header(): - return "tox comes from: %r" % (tox.__file__) - -def pytest_funcarg__newconfig(request): - tmpdir = request.getfuncargvalue("tmpdir") - def newconfig(args, source=None): - 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) - finally: - old.chdir() - return newconfig - -def pytest_funcarg__tmpdir(request): - tmpdir = request.getfuncargvalue("tmpdir") - request.addfinalizer(py.path.local().chdir) - tmpdir.chdir() - return tmpdir - -def pytest_funcarg__cmd(request): - 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 - -def pytest_funcarg__mocksession(request): - from tox._cmdline import Session - class MockSession(Session): - def __init__(self): - self._clearmocks() - self.config = request.getfuncargvalue("newconfig")([], "") - 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, - stdout=None, stderr=None, env=None): - pm = pcallMock(args, cwd, env, stdout, stderr, shell) - self._pcalls.append(pm) - return pm - return MockSession() - -def pytest_funcarg__newmocksession(request): - mocksession = request.getfuncargvalue("mocksession") - newconfig = request.getfuncargvalue("newconfig") - def newmocksession(args, source): - config = newconfig(args, source) - mocksession.config = config - 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): - argv = [str(x) for x in argv] - 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 - -def pytest_funcarg__initproj(request): - """ create a factory function for creating example projects. """ - tmpdir = request.getfuncargvalue("tmpdir") - def initproj(name, filedefs=None): - if filedefs is None: - filedefs = {} - parts = name.split("-") - if len(parts) == 1: - parts.append("0.1") - name, version = parts - 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='GPLv2 or later', - platforms=['unix', 'win32'], - packages=['%(name)s', ], - ) - ''' % locals()}) - if name not in filedefs: - create_files(base, {name: - {'__init__.py': '__version__ = %s' % version}}) - 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/_pytestplugin.py b/tox/_pytestplugin.py new file mode 100644 index 0000000..fc913d6 --- /dev/null +++ b/tox/_pytestplugin.py @@ -0,0 +1,284 @@ +import pytest, py +import tox +import os +import sys +from py.builtin import print_ +from fnmatch import fnmatch +import time +from tox._config import parseconfig +from tox._venv import VirtualEnv +from tox._cmdline import Action + +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_report_header(): + return "tox comes from: %r" % (tox.__file__) + +def pytest_funcarg__newconfig(request): + tmpdir = request.getfuncargvalue("tmpdir") + def newconfig(args, source=None): + 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) + finally: + old.chdir() + return newconfig + +def pytest_funcarg__cmd(request): + 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 + +def pytest_funcarg__mocksession(request): + from tox._cmdline import Session + class MockSession(Session): + def __init__(self): + self._clearmocks() + self.config = request.getfuncargvalue("newconfig")([], "") + 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, + stdout=None, stderr=None, env=None): + pm = pcallMock(args, cwd, env, stdout, stderr, shell) + self._pcalls.append(pm) + return pm + return MockSession() + +def pytest_funcarg__newmocksession(request): + mocksession = request.getfuncargvalue("mocksession") + newconfig = request.getfuncargvalue("newconfig") + def newmocksession(args, source): + config = newconfig(args, source) + mocksession.config = config + 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): + argv = [str(x) for x in argv] + 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(name, filedefs=None): + if filedefs is None: + filedefs = {} + parts = name.split("-") + if len(parts) == 1: + parts.append("0.1") + name, version = parts + 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='GPLv2 or later', + platforms=['unix', 'win32'], + packages=['%(name)s', ], + ) + ''' % locals()}) + if name not in filedefs: + create_files(base, {name: + {'__init__.py': '__version__ = %s' % version}}) + 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) -- cgit v1.2.1 From cdd8f99a7b52be286de34925f4cd31990f2994fe Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 23 Nov 2012 22:32:23 +0100 Subject: another fix wrt to making tox fixtures importable --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..1d4bdde --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,2 @@ + +from tox._pytestplugin import * -- cgit v1.2.1 From 14563be06745b78450b5e054ca17f2ab8b9161eb Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 23 Nov 2012 22:34:29 +0100 Subject: add Marc to changelog, contributors, bump version --- CHANGELOG | 3 +++ CONTRIBUTORS | 1 + setup.py | 2 +- tox.ini | 32 +++++++++----------------------- tox/__init__.py | 2 +- toxbootstrap.py | 2 +- 6 files changed, 16 insertions(+), 26 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3468c79..3df2a25 100755 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ 1.4.3.dev ----------------- +- merged tox-quickstart command, contributed by Marc Abramowitz, which + generates a default tox.ini after asking a few questions + - fix #48 - win32 detection of pypy and other interpreters that are on PATH (thanks Gustavo Picon) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 74a1047..e8133fc 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,6 +1,7 @@ contributions (under the MIT license): +Marc Abramowitz Sridhar Ratnakumar Chris Rose Jannis Leidl diff --git a/setup.py b/setup.py index a64d111..69ba8d3 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def main(): description='virtualenv-based automation of test activities', long_description=long_description, url='http://tox.testrun.org/', - version='1.4.3.dev1', + version='1.4.3.dev2', license='GPLv2 or later', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff --git a/tox.ini b/tox.ini index ec3da9a..80c4f9d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,26 +1,12 @@ -[tox] -envlist=py27,py26,py25,py31,py32,docs -indexserver = - testrun = http://pypi.testrun.org - pypi = http://pypi.python.org/simple +# 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. -[testenv:X] -commands=echo {posargs} +[tox] +envlist = py27, py32, py33, pypy [testenv] -commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} -deps=pytest==2.3.2 - -[testenv:docs] -basepython=python -changedir=doc -deps=:pypi:sphinx - {[testenv]deps} -commands= - py.test -v \ - --junitxml={envlogdir}/junit-{envname}.xml \ - check_sphinx.py {posargs} - -[pytest] -rsyncdirs=tests tox - +commands = nosetests -v +deps = + nose diff --git a/tox/__init__.py b/tox/__init__.py index e6669b6..ca98634 100644 --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '1.4.3.dev1' +__version__ = '1.4.3.dev2' class exception: class Error(Exception): diff --git a/toxbootstrap.py b/toxbootstrap.py index 979ea6a..c0cafc2 100644 --- a/toxbootstrap.py +++ b/toxbootstrap.py @@ -58,7 +58,7 @@ ToDo """ -__version__ = '1.4.3.dev1' +__version__ = '1.4.3.dev2' import sys import os -- cgit v1.2.1 From c25ff31f0214c02574fe4f9124f2568cb36b7ff5 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 23 Nov 2012 23:03:53 +0100 Subject: undo overwrite of tox.ini --- tox.ini | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index 80c4f9d..ec3da9a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,26 @@ -# 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 = py27, py32, py33, pypy +envlist=py27,py26,py25,py31,py32,docs +indexserver = + testrun = http://pypi.testrun.org + pypi = http://pypi.python.org/simple + +[testenv:X] +commands=echo {posargs} [testenv] -commands = nosetests -v -deps = - nose +commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} +deps=pytest==2.3.2 + +[testenv:docs] +basepython=python +changedir=doc +deps=:pypi:sphinx + {[testenv]deps} +commands= + py.test -v \ + --junitxml={envlogdir}/junit-{envname}.xml \ + check_sphinx.py {posargs} + +[pytest] +rsyncdirs=tests tox + -- cgit v1.2.1 From f44cdfba598d019431608e064a9f5a1c6ec9a5f0 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 24 Nov 2012 20:12:59 +0100 Subject: remove pytest jenkins job links --- doc/example/jenkins.txt | 2 -- tox.ini | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/example/jenkins.txt b/doc/example/jenkins.txt index 2ec67ec..262aa15 100644 --- a/doc/example/jenkins.txt +++ b/doc/example/jenkins.txt @@ -34,9 +34,7 @@ for example with ``py.test`` it is done like this: commands=py.test --junitxml=junit-{envname}.xml -See a real-life example in action with the `pytest Jenkins job`_ -.. _`pytest Jenkins job`: http://hudson.testrun.org/view/pytest/job/pytest/ **zero-installation** for slaves ------------------------------------------------------------- diff --git a/tox.ini b/tox.ini index ec3da9a..6733c8d 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ commands=echo {posargs} [testenv] commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} -deps=pytest==2.3.2 +deps=pytest==2.3.4 [testenv:docs] basepython=python -- cgit v1.2.1 From 629fae97f73e0c28edf0e8c35569c79ec94a75a5 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Sat, 24 Nov 2012 11:37:22 -0800 Subject: Add py33 to tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ec3da9a..46a5024 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,py26,py25,py31,py32,docs +envlist=py27,py26,py25,py31,py32,py33,docs indexserver = testrun = http://pypi.testrun.org pypi = http://pypi.python.org/simple -- cgit v1.2.1