Merge lp:~mvo/click/coverage into lp:click/devel

Proposed by Michael Vogt
Status: Merged
Approved by: Colin Watson
Approved revision: 462
Merged at revision: 457
Proposed branch: lp:~mvo/click/coverage
Merge into: lp:click/devel
Diff against target: 200 lines (+59/-13)
6 files modified
.coveragerc (+2/-0)
click/tests/__init__.py (+11/-2)
click/tests/gimock.py (+32/-10)
debian/control (+1/-1)
debian/rules (+6/-0)
doc/index.rst (+7/-0)
To merge this branch: bzr merge lp:~mvo/click/coverage
Reviewer Review Type Date Requested Status
Colin Watson Approve
Review via email: mp+221871@code.launchpad.net

Description of the change

This branch runs the tests with the python{,3}-coverage framework (if its installed). This gives us details about the test code coverage and can also be used to report to the QA dashboard. The coverage data is generated during the build so that jenkins can pick it up.

Feedback welcome, especially if the gimock.py changes can be done in a different/more elegant way :)

To post a comment you must log in.
lp:~mvo/click/coverage updated
458. By Michael Vogt

fix incorrect shuffle of sys import

Revision history for this message
Colin Watson (cjwatson) :
review: Needs Fixing
lp:~mvo/click/coverage updated
459. By Michael Vogt

use python3-coverage instead of python-coverage (thanks to Colin!)

460. By Michael Vogt

.coveragerc: exclude all of the tests code

Revision history for this message
Barry Warsaw (barry) wrote :

On Jun 09, 2014, at 01:41 PM, Colin Watson wrote:

>It looks like this will always re-exec with python-coverage, even if the test
>suite was run with bare python. This seems a bit inconsistent. It would be
>nice to only switch to the coverage executable if we were running under
>coverage to start with. How about 'if "coverage" in sys.modules' as a way to
>test for that?

I'm working on getting coverage for system-image, including the dbus
subprocesses. There's this page:

http://nedbatchelder.com/code/coverage/subprocess.html

I think it makes sense to set the envar COVERAGE_PROCESS_START and check for
that to know whether you're running under coverage or not.

>I know os._exit(0) caused problems for coverage, but I'm a bit concerned
>about accidentally returning into test code and causing havoc. How about
>sys.exit(0) instead, which should still generate coverage data but will
>ensure we don't double-run test code?

sys.exit() raises a SystemExit exception, but os._exit() calls exit(2), so the
latter doesn't give Python a chance to intervene.

Revision history for this message
Colin Watson (cjwatson) wrote :

On Mon, Jun 09, 2014 at 02:06:32PM -0000, Barry Warsaw wrote:
> I'm working on getting coverage for system-image, including the dbus
> subprocesses. There's this page:
>
> http://nedbatchelder.com/code/coverage/subprocess.html
>
> I think it makes sense to set the envar COVERAGE_PROCESS_START and check for
> that to know whether you're running under coverage or not.

Right, this is fine as long as we ensure that that's set consistently.
I didn't want to recommend anything overly complicated without knowing
how QA is going to be running this.

> sys.exit() raises a SystemExit exception, but os._exit() calls
> exit(2), so the latter doesn't give Python a chance to intervene.

Indeed. But I was using os._exit exactly so that Python wouldn't run
cleanups in both the parent and child processes (similarly, I generally
use _exit from forked child processes in C code). In this case coverage
is unusual in that we actively want to run its cleanup code in both
processes, so we have a choice of either making an exception
specifically for coverage in some way (seems tricky as we don't have the
coverage object in hand in gimock) or of taking special care with
everything else that might end up running twice.

lp:~mvo/click/coverage updated
461. By Michael Vogt

fix get_executable() to only use python{,3}-coverage if its really in use

462. By Michael Vogt

use sys.exit(0) instead of return in the GIMOCK_SUBPROCESS code

Revision history for this message
Michael Vogt (mvo) wrote :

Thank you very much for the review Colin! I fixed the issues you raised now.

Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.coveragerc'
2--- .coveragerc 1970-01-01 00:00:00 +0000
3+++ .coveragerc 2014-06-09 14:50:07 +0000
4@@ -0,0 +1,2 @@
5+[run]
6+omit = /usr/lib/*, /usr/local/*, click/tests/*
7\ No newline at end of file
8
9=== modified file 'click/tests/__init__.py'
10--- click/tests/__init__.py 2014-03-03 17:01:37 +0000
11+++ click/tests/__init__.py 2014-06-09 14:50:07 +0000
12@@ -16,6 +16,14 @@
13 return True
14
15
16+def get_executable():
17+ """Get python executable (respecting if python-coverage was used)"""
18+ coverage_executable = sys.executable+"-coverage"
19+ if "coverage" in sys.modules and os.path.isfile(coverage_executable):
20+ return [coverage_executable, "run", "-p"]
21+ return [sys.executable]
22+
23+
24 # Don't do any of this in interactive mode.
25 if not hasattr(sys, "ps1"):
26 _lib_click_dir = os.path.join(config.abs_top_builddir, "lib", "click")
27@@ -26,6 +34,7 @@
28 if _append_env_path("GI_TYPELIB_PATH", _lib_click_dir):
29 changed = True
30 if changed:
31+ coverage_executable = get_executable()
32 # We have to re-exec ourselves to get the dynamic loader to pick up
33 # the new value of LD_LIBRARY_PATH.
34 if "-m unittest" in sys.argv[0]:
35@@ -33,7 +42,7 @@
36 # "usefulness", making the re-exec more painful than it needs to
37 # be.
38 os.execvp(
39- sys.executable, [sys.executable, "-m", "unittest"] + sys.argv[1:])
40+ coverage_executable[0], coverage_executable + ["-m", "unittest"] + sys.argv[1:])
41 else:
42- os.execvp(sys.executable, [sys.executable] + sys.argv)
43+ os.execvp(coverage_executable[0], coverage_executable + sys.argv)
44 os._exit(1)
45
46=== modified file 'click/tests/gimock.py'
47--- click/tests/gimock.py 2014-03-10 09:30:31 +0000
48+++ click/tests/gimock.py 2014-06-09 14:50:07 +0000
49@@ -129,7 +129,7 @@
50 import xml.etree.ElementTree as etree
51
52 from click.tests.gimock_types import Stat, Stat64
53-
54+from click.tests import get_executable
55
56 # Borrowed from giscanner.girparser.
57 CORE_NS = "http://www.gtk.org/introspection/core/1.0"
58@@ -188,6 +188,11 @@
59 self._composite_refs = []
60 self._delegate_funcs = {}
61
62+ def doCleanups(self):
63+ # we do not want to run the cleanups twice, just run it in the parent
64+ if "GIMOCK_SUBPROCESS" not in os.environ:
65+ return super(GIMockTestCase, self).doCleanups()
66+
67 def _gir_get_type(self, obj):
68 ret = {}
69 arrayinfo = obj.find(_corens("array"))
70@@ -261,6 +266,7 @@
71 "stdio.h",
72 "stdint.h",
73 "stdlib.h",
74+ "string.h",
75 "sys/types.h",
76 "unistd.h",
77 ])
78@@ -292,6 +298,10 @@
79 conv["proto"] = ", ".join(
80 "%s %s" % pair for pair in zip(argtypes, argnames))
81 conv["args"] = ", ".join(argnames)
82+ if conv["ret"] == "gchar*":
83+ conv["need_strdup"] = "strdup"
84+ else:
85+ conv["need_strdup"] = ""
86 # The delegation scheme used here is needed because trying
87 # to pass pointers back and forward through ctypes is a
88 # recipe for having them truncated to 32 bits at the drop of
89@@ -338,7 +348,7 @@
90 delegate_%(name)s = 0;
91 ret = (*ctypes_%(name)s) (%(args)s);
92 if (! delegate_%(name)s)
93- return ret;
94+ return %(need_strdup)s(ret);
95 }
96 return (*real_%(name)s) (%(args)s);
97 }
98@@ -422,8 +432,8 @@
99 os.set_inheritable(wfd, True)
100 else:
101 fcntl.fcntl(rfd, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
102- args = [
103- sys.executable, "-m", "unittest",
104+ args = get_executable() + [
105+ "-m", "unittest",
106 "%s.%s.%s" % (
107 self.__class__.__module__, self.__class__.__name__,
108 self._testMethodName)]
109@@ -431,15 +441,25 @@
110 env["GIMOCK_SUBPROCESS"] = str(wfd)
111 if lib_path is not None:
112 env["LD_PRELOAD"] = lib_path
113- subp = subprocess.Popen(args, close_fds=False, env=env)
114+ subp = subprocess.Popen(args, close_fds=False, env=env,
115+ stdout=subprocess.PIPE,
116+ stderr=subprocess.PIPE)
117 os.close(wfd)
118 reader = os.fdopen(rfd, "rb")
119- subp.communicate()
120+ stdout, stderr = subp.communicate()
121 exctype = pickle.load(reader)
122- if exctype is not None and issubclass(exctype, AssertionError):
123+ # "normal" exit
124+ if exctype is not None and issubclass(exctype, SystemExit):
125+ pass
126+ elif exctype is not None and issubclass(exctype, AssertionError):
127+ print(stdout, file=sys.stdout)
128+ print(stderr, file=sys.stderr)
129 raise AssertionError("Subprocess failed a test!")
130 elif exctype is not None or subp.returncode != 0:
131- raise Exception("Subprocess returned an error!")
132+ print(stdout, file=sys.stdout)
133+ print(stderr, file=sys.stderr)
134+ raise Exception("Subprocess returned an error! (%s, %s)" % (
135+ exctype, subp.returncode))
136 reader.close()
137 raise ParentProcess()
138
139@@ -448,9 +468,11 @@
140 if "GIMOCK_SUBPROCESS" in os.environ:
141 wfd = int(os.environ["GIMOCK_SUBPROCESS"])
142 writer = os.fdopen(wfd, "wb")
143- pickle.dump(None, writer)
144+ # a sys.ext will generate a SystemExit exception, so we
145+ # push it into the pipe so that the parent knows whats going on
146+ pickle.dump(SystemExit, writer)
147 writer.flush()
148- os._exit(0)
149+ sys.exit(0)
150 except ParentProcess:
151 pass
152 except Exception as e:
153
154=== modified file 'debian/control'
155--- debian/control 2014-06-03 16:18:38 +0000
156+++ debian/control 2014-06-09 14:50:07 +0000
157@@ -3,7 +3,7 @@
158 Priority: optional
159 Maintainer: Colin Watson <cjwatson@ubuntu.com>
160 Standards-Version: 3.9.5
161-Build-Depends: debhelper (>= 9~), dh-autoreconf, intltool, python3:any (>= 3.2), python3-all:any, python3-setuptools, python3-apt, python3-debian, python3-gi, python3:any (>= 3.3) | python3-mock, pep8, python3-pep8, pyflakes, python3-sphinx, pkg-config, valac, gobject-introspection (>= 0.6.7), libgirepository1.0-dev (>= 0.6.7), libglib2.0-dev (>= 2.34), gir1.2-glib-2.0, libjson-glib-dev (>= 0.10), libgee-0.8-dev, libpackagekit-glib2-dev (>= 0.7.2)
162+Build-Depends: debhelper (>= 9~), dh-autoreconf, intltool, python3:any (>= 3.2), python3-all:any, python3-setuptools, python3-apt, python3-debian, python3-gi, python3:any (>= 3.3) | python3-mock, pep8, python3-pep8, pyflakes, python3-sphinx, pkg-config, valac, gobject-introspection (>= 0.6.7), libgirepository1.0-dev (>= 0.6.7), libglib2.0-dev (>= 2.34), gir1.2-glib-2.0, libjson-glib-dev (>= 0.10), libgee-0.8-dev, libpackagekit-glib2-dev (>= 0.7.2), python3-coverage
163 Vcs-Bzr: https://code.launchpad.net/~ubuntu-managed-branches/click/click
164 Vcs-Browser: http://bazaar.launchpad.net/~ubuntu-managed-branches/click/click/files
165 X-Auto-Uploader: no-rewrite-version
166
167=== modified file 'debian/rules'
168--- debian/rules 2014-03-05 16:44:29 +0000
169+++ debian/rules 2014-06-09 14:50:07 +0000
170@@ -43,6 +43,12 @@
171 dh_auto_clean
172 $(MAKE) -C doc clean
173
174+override_dh_auto_test:
175+ python3-coverage run -m unittest discover click.tests
176+ python3-coverage combine
177+ python3-coverage xml
178+ python3-coverage report
179+
180 PYTHON_INSTALL_FLAGS := \
181 --force --root=$(CURDIR)/debian/tmp \
182 --no-compile -O0 --install-layout=deb
183
184=== modified file 'doc/index.rst'
185--- doc/index.rst 2014-05-19 13:15:56 +0000
186+++ doc/index.rst 2014-06-09 14:50:07 +0000
187@@ -90,6 +90,13 @@
188
189 $ python2 -m unittest click.tests.test_build.TestClickBuilder.test_build
190
191+If you have python-coverage installed, you can get a test coverage report
192+by typing:
193+
194+ $ python-coverage combine
195+ $ python-coverage report
196+
197+This works also for python3-coverage.
198
199
200 Documentation

Subscribers

People subscribed via source and target branches

to all changes: