Author: Armin Rigo <ar...@tunes.org> Branch: Changeset: r45873:63a68e798693 Date: 2011-07-22 13:24 +0200 http://bitbucket.org/pypy/pypy/changeset/63a68e798693/
Log: merge heads diff --git a/lib-python/modified-2.7/distutils/sysconfig_pypy.py b/lib-python/modified-2.7/distutils/sysconfig_pypy.py --- a/lib-python/modified-2.7/distutils/sysconfig_pypy.py +++ b/lib-python/modified-2.7/distutils/sysconfig_pypy.py @@ -116,6 +116,12 @@ if compiler.compiler_type == "unix": compiler.compiler_so.extend(['-fPIC', '-Wimplicit']) compiler.shared_lib_extension = get_config_var('SO') + if "CFLAGS" in os.environ: + cflags = os.environ["CFLAGS"] + compiler.compiler.append(cflags) + compiler.compiler_so.append(cflags) + compiler.linker_so.append(cflags) + from sysconfig_cpython import ( parse_makefile, _variable_rx, expand_makefile_vars) diff --git a/pypy/doc/coding-guide.rst b/pypy/doc/coding-guide.rst --- a/pypy/doc/coding-guide.rst +++ b/pypy/doc/coding-guide.rst @@ -929,6 +929,19 @@ located in the ``py/bin/`` directory. For switches to modify test execution pass the ``-h`` option. +Coverage reports +---------------- + +In order to get coverage reports the `pytest-cov`_ plugin is included. +it adds some extra requirements ( coverage_ and `cov-core`_ ) +and can once they are installed coverage testing can be invoked via:: + + python test_all.py --cov file_or_direcory_to_cover file_or_directory + +.. _`pytest-cov`: http://pypi.python.org/pypi/pytest-cov +.. _`coverage`: http://pypi.python.org/pypi/coverage +.. _`cov-core`: http://pypi.python.org/pypi/cov-core + Test conventions ---------------- diff --git a/pypy/doc/windows.rst b/pypy/doc/windows.rst --- a/pypy/doc/windows.rst +++ b/pypy/doc/windows.rst @@ -32,6 +32,15 @@ modules that relies on third-party libraries. See below how to get and build them. +Preping Windows for the Large Build +----------------------------------- + +Follow http://usa.autodesk.com/adsk/servlet/ps/dl/item?siteID=123112&id=9583842&linkID=9240617 to allow Windows up to 3GB for 32bit applications if you are on a 32bit version of windows. If you are using Visual C++ 2008 (untested with 2005), then you will have a utility called editbin.exe within Visual Studio 9.0\VC\bin. You will need to execute:: + + editbin /largeaddressaware pypy.exe + +on the pypy.exe or python.exe you are using to buld the new pypy. Reboot now if you followed the 3G instructions for 32bit windows. + Installing external packages ---------------------------- diff --git a/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py b/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py --- a/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py +++ b/pypy/jit/metainterp/optimizeopt/test/test_optimizeopt.py @@ -2820,11 +2820,11 @@ def test_residual_call_invalidate_some_arrays(self): ops = """ [p1, p2, i1] - p3 = getarrayitem_gc(p1, 0, descr=arraydescr2) + p3 = getarrayitem_gc(p2, 0, descr=arraydescr2) p4 = getarrayitem_gc(p2, 1, descr=arraydescr2) i2 = getarrayitem_gc(p1, 1, descr=arraydescr) i3 = call(i1, descr=writearraydescr) - p5 = getarrayitem_gc(p1, 0, descr=arraydescr2) + p5 = getarrayitem_gc(p2, 0, descr=arraydescr2) p6 = getarrayitem_gc(p2, 1, descr=arraydescr2) i4 = getarrayitem_gc(p1, 1, descr=arraydescr) escape(p3) @@ -2837,7 +2837,7 @@ """ expected = """ [p1, p2, i1] - p3 = getarrayitem_gc(p1, 0, descr=arraydescr2) + p3 = getarrayitem_gc(p2, 0, descr=arraydescr2) p4 = getarrayitem_gc(p2, 1, descr=arraydescr2) i2 = getarrayitem_gc(p1, 1, descr=arraydescr) i3 = call(i1, descr=writearraydescr) diff --git a/pypy/jit/metainterp/test/test_ajit.py b/pypy/jit/metainterp/test/test_ajit.py --- a/pypy/jit/metainterp/test/test_ajit.py +++ b/pypy/jit/metainterp/test/test_ajit.py @@ -2586,7 +2586,23 @@ return n res = self.meta_interp(f, [10, 1]) self.check_loops(getfield_gc=2) + assert res == f(10, 1) + def test_jit_merge_point_with_raw_pointer(self): + driver = JitDriver(greens = [], reds = ['n', 'x']) + + TP = lltype.Array(lltype.Signed, hints={'nolength': True}) + + def f(n): + x = lltype.malloc(TP, 10, flavor='raw') + x[0] = 1 + while n > 0: + driver.jit_merge_point(n=n, x=x) + n -= x[0] + lltype.free(x, flavor='raw') + return n + + self.meta_interp(f, [10], repeat=3) class TestLLtype(BaseLLtypeTests, LLJitMixin): pass diff --git a/pypy/jit/metainterp/warmstate.py b/pypy/jit/metainterp/warmstate.py --- a/pypy/jit/metainterp/warmstate.py +++ b/pypy/jit/metainterp/warmstate.py @@ -138,6 +138,9 @@ refvalue = cpu.ts.cast_to_ref(value) cpu.set_future_value_ref(j, refvalue) elif typecode == 'int': + if isinstance(lltype.typeOf(value), lltype.Ptr): + intvalue = llmemory.AddressAsInt(llmemory.cast_ptr_to_adr(value)) + else: intvalue = lltype.cast_primitive(lltype.Signed, value) cpu.set_future_value_int(j, intvalue) elif typecode == 'float': diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -11,29 +11,15 @@ from pypy.tool.sourcetools import func_with_new_name import math -def dummy1(v): - assert isinstance(v, float) - return v - -def dummy2(v): - assert isinstance(v, float) - return v - TP = lltype.Array(lltype.Float, hints={'nolength': True}) numpy_driver = jit.JitDriver(greens = ['signature'], reds = ['result_size', 'i', 'self', 'result']) all_driver = jit.JitDriver(greens=['signature'], reds=['i', 'size', 'self']) any_driver = jit.JitDriver(greens=['signature'], reds=['i', 'size', 'self']) -slice_driver1 = jit.JitDriver(greens=['signature'], reds=['i', 'j', 'step', 'stop', 'storage', 'arr']) -slice_driver2 = jit.JitDriver(greens=['signature'], reds=['i', 'j', 'step', 'stop', 'storage', 'arr']) +slice_driver1 = jit.JitDriver(greens=['signature'], reds=['i', 'j', 'step', 'stop', 'source', 'dest']) +slice_driver2 = jit.JitDriver(greens=['signature'], reds=['i', 'j', 'step', 'stop', 'source', 'dest']) -def pos(v): - return v -def neg(v): - return -v -def absolute(v): - return abs(v) def add(v1, v2): return v1 + v2 def mul(v1, v2): @@ -59,21 +45,14 @@ arr.force_if_needed() del self.invalidates[:] - def _unop_impl(function): - signature = Signature() + def _unaryop_impl(w_ufunc): def impl(self, space): - new_sig = self.signature.transition(signature) - res = Call1( - function, - self, - new_sig) - self.invalidates.append(res) - return space.wrap(res) - return func_with_new_name(impl, "uniop_%s_impl" % function.__name__) + return w_ufunc(space, self) + return func_with_new_name(impl, "unaryop_%s_impl" % w_ufunc.__name__) - descr_pos = _unop_impl(pos) - descr_neg = _unop_impl(neg) - descr_abs = _unop_impl(absolute) + descr_pos = _unaryop_impl(interp_ufuncs.positive) + descr_neg = _unaryop_impl(interp_ufuncs.negative) + descr_abs = _unaryop_impl(interp_ufuncs.absolute) def _binop_impl(w_ufunc): def impl(self, space, w_other): @@ -268,23 +247,25 @@ def descr_mean(self, space): return space.wrap(space.float_w(self.descr_sum(space))/self.find_size()) - def _sliceloop1(self, start, stop, step, arr, storage): + def _sliceloop1(self, start, stop, step, source, dest): i = start j = 0 while i < stop: - slice_driver1.jit_merge_point(signature=arr.signature, - step=step, stop=stop, i=i, j=j, arr=arr, storage=storage) - storage[i] = arr.eval(j) + slice_driver1.jit_merge_point(signature=source.signature, + step=step, stop=stop, i=i, j=j, source=source, + dest=dest) + dest.storage[i] = source.eval(j) j += 1 i += step - def _sliceloop2(self, start, stop, step, arr, storage): + def _sliceloop2(self, start, stop, step, source, dest): i = start j = 0 while i > stop: - slice_driver2.jit_merge_point(signature=arr.signature, - step=step, stop=stop, i=i, j=j, arr=arr, storage=storage) - storage[i] = arr.eval(j) + slice_driver2.jit_merge_point(signature=source.signature, + step=step, stop=stop, i=i, j=j, source=source, + dest=dest) + dest.storage[i] = source.eval(j) j += 1 i += step @@ -469,9 +450,9 @@ stop = self.calc_index(stop) step = self.step * step if step > 0: - self._sliceloop1(start, stop, step, arr, self.parent.storage) + self._sliceloop1(start, stop, step, arr, self.parent) else: - self._sliceloop2(start, stop, step, arr, self.parent.storage) + self._sliceloop2(start, stop, step, arr, self.parent) def calc_index(self, item): return (self.start + item * self.step) @@ -510,9 +491,9 @@ if not isinstance(arr, BaseArray): arr = convert_to_array(space, arr) if step > 0: - self._sliceloop1(start, stop, step, arr, self.storage) + self._sliceloop1(start, stop, step, arr, self) else: - self._sliceloop2(start, stop, step, arr, self.storage) + self._sliceloop2(start, stop, step, arr, self) def __del__(self): lltype.free(self.storage, flavor='raw') diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -72,6 +72,11 @@ def multiply(lvalue, rvalue): return lvalue * rvalue +# Used by numarray for __pos__. Not visible from numpy application space. +@ufunc +def positive(value): + return value + @ufunc def negative(value): return -value @@ -114,4 +119,4 @@ @ufunc2 def mod(lvalue, rvalue): - return math.fmod(lvalue, rvalue) \ No newline at end of file + return math.fmod(lvalue, rvalue) diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -171,6 +171,12 @@ for i in range(5): assert b[i] == i + 5 + def test_radd(self): + from numpy import array + r = 3 + array(range(3)) + for i in range(3): + assert r[i] == i + 3 + def test_add_list(self): from numpy import array a = array(range(5)) diff --git a/pypy/module/micronumpy/test/test_zjit.py b/pypy/module/micronumpy/test/test_zjit.py --- a/pypy/module/micronumpy/test/test_zjit.py +++ b/pypy/module/micronumpy/test/test_zjit.py @@ -1,7 +1,7 @@ from pypy.jit.metainterp.test.support import LLJitMixin from pypy.rpython.test.test_llinterp import interpret from pypy.module.micronumpy.interp_numarray import (SingleDimArray, Signature, - FloatWrapper, Call2, SingleDimSlice, add, mul, neg, Call1) + FloatWrapper, Call2, SingleDimSlice, add, mul, Call1) from pypy.module.micronumpy.interp_ufuncs import negative from pypy.module.micronumpy.compile import numpy_compile from pypy.rlib.objectmodel import specialize @@ -48,19 +48,6 @@ "int_lt": 1, "guard_true": 1, "jump": 1}) assert result == f(5) - def test_neg(self): - def f(i): - ar = SingleDimArray(i) - v = Call1(neg, ar, Signature()) - return v.get_concrete().storage[3] - - result = self.meta_interp(f, [5], listops=True, backendopt=True) - self.check_loops({"getarrayitem_raw": 1, "float_neg": 1, - "setarrayitem_raw": 1, "int_add": 1, - "int_lt": 1, "guard_true": 1, "jump": 1}) - - assert result == f(5) - def test_sum(self): space = self.space diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py --- a/pypy/module/pypyjit/test_pypy_c/test_string.py +++ b/pypy/module/pypyjit/test_pypy_c/test_string.py @@ -92,10 +92,10 @@ p51 = new_with_vtable(21136408) setfield_gc(p51, p28, descr=<GcPtrFieldDescr .*NumberStringParser.inst_literal .*>) setfield_gc(p51, ConstPtr(ptr51), descr=<GcPtrFieldDescr pypy.objspace.std.strutil.NumberStringParser.inst_fname .*>) - setfield_gc(p51, i29, descr=<SignedFieldDescr .*NumberStringParser.inst_n .*>) setfield_gc(p51, 1, descr=<SignedFieldDescr .*NumberStringParser.inst_sign .*>) setfield_gc(p51, 16, descr=<SignedFieldDescr .*NumberStringParser.inst_base .*>) setfield_gc(p51, p28, descr=<GcPtrFieldDescr .*NumberStringParser.inst_s .*>) + setfield_gc(p51, i29, descr=<SignedFieldDescr .*NumberStringParser.inst_n .*>) p55 = call(ConstClass(parse_digit_string), p51, descr=<GcPtrCallDescr>) guard_no_exception(descr=...) i57 = call(ConstClass(rbigint.toint), p55, descr=<SignedCallDescr>) @@ -104,4 +104,4 @@ guard_no_overflow(descr=...) --TICK-- jump(p0, p1, p2, p3, p4, p5, i58, i7, i8, p9, p10, descr=<Loop4>) - """) \ No newline at end of file + """) diff --git a/pypy/module/select/test/test_epoll.py b/pypy/module/select/test/test_epoll.py --- a/pypy/module/select/test/test_epoll.py +++ b/pypy/module/select/test/test_epoll.py @@ -138,7 +138,7 @@ expected.sort() assert events == expected - assert then - now < 0.01 + assert then - now < 0.02 now = time.time() events = ep.poll(timeout=2.1, maxevents=4) @@ -151,7 +151,7 @@ now = time.time() events = ep.poll(1, 4) then = time.time() - assert then - now < 0.01 + assert then - now < 0.02 events.sort() expected = [ @@ -168,7 +168,7 @@ now = time.time() events = ep.poll(1, 4) then = time.time() - assert then - now < 0.01 + assert then - now < 0.02 expected = [(server.fileno(), select.EPOLLOUT)] assert events == expected @@ -192,7 +192,7 @@ now = time.time() ep.poll(1, 4) then = time.time() - assert then - now < 0.01 + assert then - now < 0.02 server.close() ep.unregister(fd) diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py --- a/pypy/objspace/std/dictproxyobject.py +++ b/pypy/objspace/std/dictproxyobject.py @@ -86,7 +86,7 @@ def clear(self, w_dict): self.unerase(w_dict.dstorage).dict_w.clear() - self.unerase(w_dict.dstorage).mutated() + self.unerase(w_dict.dstorage).mutated(None) class DictProxyIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): diff --git a/pypy/objspace/std/test/test_identitydict.py b/pypy/objspace/std/test/test_identitydict.py --- a/pypy/objspace/std/test/test_identitydict.py +++ b/pypy/objspace/std/test/test_identitydict.py @@ -1,3 +1,4 @@ +import py from pypy.interpreter.gateway import interp2app from pypy.conftest import gettestobjspace from pypy.conftest import option @@ -8,6 +9,8 @@ from pypy.objspace.std import identitydict cls.space = gettestobjspace( **{"objspace.std.withidentitydict": True}) + if option.runappdirect: + py.test.skip("interp2app doesn't work on appdirect") def compares_by_identity(space, w_cls): return space.wrap(w_cls.compares_by_identity()) @@ -49,7 +52,7 @@ def setup_class(cls): cls.space = gettestobjspace(**{"objspace.std.withidentitydict": True}) if option.runappdirect: - py.test.skip("__repr__ doesn't work on appdirect") + py.test.skip("interp2app doesn't work on appdirect") def w_uses_identity_strategy(self, obj): import __pypy__ diff --git a/pypy/rlib/streamio.py b/pypy/rlib/streamio.py --- a/pypy/rlib/streamio.py +++ b/pypy/rlib/streamio.py @@ -875,28 +875,32 @@ if bufsize == -1: # Get default from the class bufsize = self.bufsize self.bufsize = bufsize # buffer size (hint only) - self.buf = "" + self.buf = [] + self.buflen = 0 def flush_buffers(self): if self.buf: - self.do_write(self.buf) - self.buf = "" + self.do_write(''.join(self.buf)) + self.buf = [] + self.buflen = 0 def tell(self): - return self.do_tell() + len(self.buf) + return self.do_tell() + self.buflen def write(self, data): - buflen = len(self.buf) + buflen = self.buflen datalen = len(data) if datalen + buflen < self.bufsize: - self.buf += data + self.buf.append(data) + self.buflen += datalen elif buflen: - slice = self.bufsize - buflen - assert slice >= 0 - self.buf += data[:slice] - self.do_write(self.buf) - self.buf = "" - self.write(data[slice:]) + i = self.bufsize - buflen + assert i >= 0 + self.buf.append(data[:i]) + self.do_write(''.join(self.buf)) + self.buf = [] + self.buflen = 0 + self.write(data[i:]) else: self.do_write(data) @@ -922,11 +926,27 @@ """ def write(self, data): - BufferingOutputStream.write(self, data) - p = self.buf.rfind('\n') + 1 - if p >= 0: - self.do_write(self.buf[:p]) - self.buf = self.buf[p:] + p = data.rfind('\n') + 1 + assert p >= 0 + if self.buflen + len(data) < self.bufsize: + if p == 0: + self.buf.append(data) + self.buflen += len(data) + else: + if self.buflen: + self.do_write(''.join(self.buf)) + self.do_write(data[:p]) + self.buf = [data[p:]] + self.buflen = len(self.buf[0]) + else: + if self.buflen + p < self.bufsize: + p = self.bufsize - self.buflen + if self.buflen: + self.do_write(''.join(self.buf)) + assert p >= 0 + self.do_write(data[:p]) + self.buf = [data[p:]] + self.buflen = len(self.buf[0]) # ____________________________________________________________ diff --git a/pypy/test_all.py b/pypy/test_all.py --- a/pypy/test_all.py +++ b/pypy/test_all.py @@ -18,4 +18,5 @@ if __name__ == '__main__': import tool.autopath import pytest - sys.exit(pytest.main()) + import pytest_cov + sys.exit(pytest.main(plugins=[pytest_cov])) diff --git a/pypy/tool/jitlogparser/parser.py b/pypy/tool/jitlogparser/parser.py --- a/pypy/tool/jitlogparser/parser.py +++ b/pypy/tool/jitlogparser/parser.py @@ -30,6 +30,9 @@ def getres(self): return self._getvar(self.res) + def getdescr(self): + return self.descr + def _getvar(self, v): return v @@ -37,9 +40,9 @@ return self._is_guard def repr(self): - args = self.args + args = self.getargs() if self.descr is not None: - args.append('descr=%s' % self.descr) + args.append('descr=%s' % self.getdescr()) arglist = ', '.join(args) if self.res is not None: return '%s = %s(%s)' % (self.getres(), self.name, arglist) diff --git a/pypy/tool/jitlogparser/test/test_parser.py b/pypy/tool/jitlogparser/test/test_parser.py --- a/pypy/tool/jitlogparser/test/test_parser.py +++ b/pypy/tool/jitlogparser/test/test_parser.py @@ -1,6 +1,6 @@ from pypy.tool.jitlogparser.parser import (SimpleParser, TraceForOpcode, Function, adjust_bridges, - import_log) + import_log, Op) from pypy.tool.jitlogparser.storage import LoopStorage import py, sys @@ -225,3 +225,9 @@ assert 'cmp' in loops[1].operations[1].asm # bridge assert 'jo' in loops[3].operations[3].asm + +def test_Op_repr_is_pure(): + op = Op('foobar', ['a', 'b'], 'c', 'mydescr') + myrepr = 'c = foobar(a, b, descr=mydescr)' + assert op.repr() == myrepr + assert op.repr() == myrepr # do it twice diff --git a/pypy/tool/release/win32build.py b/pypy/tool/release/win32build.py --- a/pypy/tool/release/win32build.py +++ b/pypy/tool/release/win32build.py @@ -24,6 +24,6 @@ shutil.copy(str(pypydir.join('..', '..', 'expat-2.0.1', 'win32', 'bin', 'release', 'libexpat.dll')), str(builddir)) make_pypy('', ['-Ojit']) -make_pypy('-nojit', []) +make_pypy('-nojit', ['-O2']) #make_pypy('-stackless', [--stackless]) #make_pypy('-sandbox', [--sandbox]) diff --git a/pytest.py b/pytest.py --- a/pytest.py +++ b/pytest.py @@ -9,6 +9,8 @@ from _pytest import __version__ if __name__ == '__main__': # if run as a script or by 'python -m pytest' - raise SystemExit(main()) + #XXX: sync to upstream later + import pytest_cov + raise SystemExit(main(plugins=[pytest_cov])) else: _preloadplugins() # to populate pytest.* namespace so help(pytest) works diff --git a/pytest_cov.py b/pytest_cov.py new file mode 100644 --- /dev/null +++ b/pytest_cov.py @@ -0,0 +1,353 @@ +"""produce code coverage reports using the 'coverage' package, including support for distributed testing. + +This plugin produces coverage reports. It supports centralised testing and distributed testing in +both load and each modes. It also supports coverage of subprocesses. + +All features offered by the coverage package should be available, either through pytest-cov or +through coverage's config file. + + +Installation +------------ + +The `pytest-cov`_ package may be installed with pip or easy_install:: + + pip install pytest-cov + easy_install pytest-cov + +.. _`pytest-cov`: http://pypi.python.org/pypi/pytest-cov/ + + +Uninstallation +-------------- + +Uninstalling packages is supported by pip:: + + pip uninstall pytest-cov + +However easy_install does not provide an uninstall facility. + +.. IMPORTANT:: + + Ensure that you manually delete the init_cov_core.pth file in your site-packages directory. + + This file starts coverage collection of subprocesses if appropriate during site initialisation + at python startup. + + +Usage +----- + +Centralised Testing +~~~~~~~~~~~~~~~~~~~ + +Centralised testing will report on the combined coverage of the main process and all of it's +subprocesses. + +Running centralised testing:: + + py.test --cov myproj tests/ + +Shows a terminal report:: + + -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- + Name Stmts Miss Cover + ---------------------------------------- + myproj/__init__ 2 0 100% + myproj/myproj 257 13 94% + myproj/feature4286 94 7 92% + ---------------------------------------- + TOTAL 353 20 94% + + +Distributed Testing: Load +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Distributed testing with dist mode set to load will report on the combined coverage of all slaves. +The slaves may be spread out over any number of hosts and each slave may be located anywhere on the +file system. Each slave will have it's subprocesses measured. + +Running distributed testing with dist mode set to load:: + + py.test --cov myproj -n 2 tests/ + +Shows a terminal report:: + + -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- + Name Stmts Miss Cover + ---------------------------------------- + myproj/__init__ 2 0 100% + myproj/myproj 257 13 94% + myproj/feature4286 94 7 92% + ---------------------------------------- + TOTAL 353 20 94% + + +Again but spread over different hosts and different directories:: + + py.test --cov myproj --dist load + --tx ssh=memedough@host1//chdir=testenv1 + --tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python + --rsyncdir myproj --rsyncdir tests --rsync examples + tests/ + +Shows a terminal report:: + + -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- + Name Stmts Miss Cover + ---------------------------------------- + myproj/__init__ 2 0 100% + myproj/myproj 257 13 94% + myproj/feature4286 94 7 92% + ---------------------------------------- + TOTAL 353 20 94% + + +Distributed Testing: Each +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Distributed testing with dist mode set to each will report on the combined coverage of all slaves. +Since each slave is running all tests this allows generating a combined coverage report for multiple +environments. + +Running distributed testing with dist mode set to each:: + + py.test --cov myproj --dist each + --tx popen//chdir=/tmp/testenv3//python=/usr/local/python27/bin/python + --tx ssh=memedough@host2//chdir=/tmp/testenv4//python=/tmp/env2/bin/python + --rsyncdir myproj --rsyncdir tests --rsync examples + tests/ + +Shows a terminal report:: + + ---------------------------------------- coverage ---------------------------------------- + platform linux2, python 2.6.5-final-0 + platform linux2, python 2.7.0-final-0 + Name Stmts Miss Cover + ---------------------------------------- + myproj/__init__ 2 0 100% + myproj/myproj 257 13 94% + myproj/feature4286 94 7 92% + ---------------------------------------- + TOTAL 353 20 94% + + +Reporting +--------- + +It is possible to generate any combination of the reports for a single test run. + +The available reports are terminal (with or without missing line numbers shown), HTML, XML and +annotated source code. + +The terminal report without line numbers (default):: + + py.test --cov-report term --cov myproj tests/ + + -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- + Name Stmts Miss Cover + ---------------------------------------- + myproj/__init__ 2 0 100% + myproj/myproj 257 13 94% + myproj/feature4286 94 7 92% + ---------------------------------------- + TOTAL 353 20 94% + + +The terminal report with line numbers:: + + py.test --cov-report term-missing --cov myproj tests/ + + -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- + Name Stmts Miss Cover Missing + -------------------------------------------------- + myproj/__init__ 2 0 100% + myproj/myproj 257 13 94% 24-26, 99, 149, 233-236, 297-298, 369-370 + myproj/feature4286 94 7 92% 183-188, 197 + -------------------------------------------------- + TOTAL 353 20 94% + + +The remaining three reports output to files without showing anything on the terminal (useful for +when the output is going to a continuous integration server):: + + py.test --cov-report html + --cov-report xml + --cov-report annotate + --cov myproj tests/ + + +Coverage Data File +------------------ + +The data file is erased at the beginning of testing to ensure clean data for each test run. + +The data file is left at the end of testing so that it is possible to use normal coverage tools to +examine it. + + +Coverage Config File +-------------------- + +This plugin provides a clean minimal set of command line options that are added to pytest. For +further control of coverage use a coverage config file. + +For example if tests are contained within the directory tree being measured the tests may be +excluded if desired by using a .coveragerc file with the omit option set:: + + py.test --cov-config .coveragerc + --cov myproj + myproj/tests/ + +Where the .coveragerc file contains file globs:: + + [run] + omit = tests/* + +For full details refer to the `coverage config file`_ documentation. + +.. _`coverage config file`: http://nedbatchelder.com/code/coverage/config.html + +Note that this plugin controls some options and setting the option in the config file will have no +effect. These include specifying source to be measured (source option) and all data file handling +(data_file and parallel options). + + +Limitations +----------- + +For distributed testing the slaves must have the pytest-cov package installed. This is needed since +the plugin must be registered through setuptools / distribute for pytest to start the plugin on the +slave. + +For subprocess measurement environment variables must make it from the main process to the +subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess must +do normal site initialisation so that the environment variables can be detected and coverage +started. + + +Acknowledgements +---------------- + +Whilst this plugin has been built fresh from the ground up it has been influenced by the work done +on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and nose-cover (Jason Pellerin) which are +other coverage plugins. + +Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs. + +Holger Krekel for pytest with its distributed testing support. + +Jason Pellerin for nose. + +Michael Foord for unittest2. + +No doubt others have contributed to these tools as well. +""" + + +def pytest_addoption(parser): + """Add options to control coverage.""" + + group = parser.getgroup('coverage reporting with distributed testing support') + group.addoption('--cov', action='append', default=[], metavar='path', + dest='cov_source', + help='measure coverage for filesystem path (multi-allowed)') + group.addoption('--cov-report', action='append', default=[], metavar='type', + choices=['term', 'term-missing', 'annotate', 'html', 'xml'], + dest='cov_report', + help='type of report to generate: term, term-missing, annotate, html, xml (multi-allowed)') + group.addoption('--cov-config', action='store', default='.coveragerc', metavar='path', + dest='cov_config', + help='config file for coverage, default: .coveragerc') + + +def pytest_configure(config): + """Activate coverage plugin if appropriate.""" + + if config.getvalue('cov_source'): + config.pluginmanager.register(CovPlugin(), '_cov') + + +class CovPlugin(object): + """Use coverage package to produce code coverage reports. + + Delegates all work to a particular implementation based on whether + this test process is centralised, a distributed master or a + distributed slave. + """ + + def __init__(self): + """Creates a coverage pytest plugin. + + We read the rc file that coverage uses to get the data file + name. This is needed since we give coverage through it's API + the data file name. + """ + + # Our implementation is unknown at this time. + self.cov_controller = None + + def pytest_sessionstart(self, session): + """At session start determine our implementation and delegate to it.""" + + import cov_core + + cov_source = session.config.getvalue('cov_source') + cov_report = session.config.getvalue('cov_report') or ['term'] + cov_config = session.config.getvalue('cov_config') + + session_name = session.__class__.__name__ + is_master = (session.config.pluginmanager.hasplugin('dsession') or + session_name == 'DSession') + is_slave = (hasattr(session.config, 'slaveinput') or + session_name == 'SlaveSession') + nodeid = None + + if is_master: + controller_cls = cov_core.DistMaster + elif is_slave: + controller_cls = cov_core.DistSlave + nodeid = session.config.slaveinput.get('slaveid', getattr(session, 'nodeid')) + else: + controller_cls = cov_core.Central + + self.cov_controller = controller_cls(cov_source, + cov_report, + cov_config, + session.config, + nodeid) + + self.cov_controller.start() + + def pytest_configure_node(self, node): + """Delegate to our implementation.""" + + self.cov_controller.configure_node(node) + pytest_configure_node.optionalhook = True + + def pytest_testnodedown(self, node, error): + """Delegate to our implementation.""" + + self.cov_controller.testnodedown(node, error) + pytest_testnodedown.optionalhook = True + + def pytest_sessionfinish(self, session, exitstatus): + """Delegate to our implementation.""" + + self.cov_controller.finish() + + def pytest_terminal_summary(self, terminalreporter): + """Delegate to our implementation.""" + + self.cov_controller.summary(terminalreporter._tw) + + +def pytest_funcarg__cov(request): + """A pytest funcarg that provides access to the underlying coverage object.""" + + # Check with hasplugin to avoid getplugin exception in older pytest. + if request.config.pluginmanager.hasplugin('_cov'): + plugin = request.config.pluginmanager.getplugin('_cov') + if plugin.cov_controller: + return plugin.cov_controller.cov + return None _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit