Merge lp:~stefanor/objgraph/pure3k into lp:objgraph
- pure3k
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merge reported by: | Marius Gedminas | ||||
Merged at revision: | not available | ||||
Proposed branch: | lp:~stefanor/objgraph/pure3k | ||||
Merge into: | lp:objgraph | ||||
Diff against target: |
441 lines (+135/-58) 8 files modified
Makefile (+10/-4) generator-sample.txt (+2/-2) index.txt (+14/-1) objgraph.py (+64/-36) references.txt (+2/-2) setup.py (+18/-5) tests.py (+24/-7) uncollectable.txt (+1/-1) |
||||
To merge this branch: | bzr merge lp:~stefanor/objgraph/pure3k | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Marius Gedminas | Approve | ||
Review via email: mp+43725@code.launchpad.net |
Commit message
Description of the change
Python 3.x compatibility.
I'm creating this merge proposal purely so that I could comment on changes made in this branch.
Marius Gedminas (mgedmin) wrote : | # |
I completely forgot one important thing:
- tests need to be fixed so they actually pass!
Incidentally, do you know any good image comparison tool, usable from the command line with
bzr diff --using=diffprogram
? I'm using a shell script wrapper around imagemagick's compare, which is nice, but only works when the images are exactly the same size.
Marius Gedminas (mgedmin) wrote : | # |
references.txt needs a fix: instead of using [range(7)] it should use [list(range(7))]. Otherwise Python 3.x uses a single range object instead of multiple int objects, and the test cannot test the output suppression bits.
Marius Gedminas (mgedmin) wrote : | # |
I'm seeing spurious 2-tuples in some of the graphs generated by 'make images PYTHON=python3.1', e.g. refcounts.png. After adding some instrumentation, I can see that it contains (<code object>, <f_locals-
I'm not sure what to do about them -- try to suppress or make the tests accept them?
I'd rather display an accurate picture of the situation, so I'm trying to suppress only those objects that are introduced by objgraph itself. So, I think the right thing here is to change the doctest output checker (tests.MyChecker) and have it accept an arbitrary number of nodes somehow... regexp search and replace of "(\d+ nodes)" with "(X nodes)" in both 'want' and 'got'?
Marius Gedminas (mgedmin) wrote : | # |
> Incidentally, do you know any good image comparison tool, usable from the
> command line with
>
> bzr diff --using=diffprogram
>
> ? I'm using a shell script wrapper around imagemagick's compare, which is
> nice, but only works when the images are exactly the same size.
And now I'm using this:
bzr diff --using='imgdiff --lr --viewer eog' *.png
where imgdiff is http://
Marius Gedminas (mgedmin) wrote : | # |
> So, I
> think the right thing here is to change the doctest output checker
> (tests.MyChecker) and have it accept an arbitrary number of nodes somehow...
> regexp search and replace of "(\d+ nodes)" with "(X nodes)" in both 'want' and
> 'got'?
This should be done for 'make test', but 'make images' should not ignore node numbers. I want the documentation text to match the images. This is easily achieved by having two different checker classes, one of them used in tests.py, and the other one used in setup.py.
Stefano Rivera (stefanor) wrote : | # |
> I'd like Python 2.4/5 compatibility back pretty please :)
Righto, /me digs out an etch VM that has python2.4.
> Is rev. 84 necessary?
No, it isn't.
> tests need to be fixed so they actually pass!
Yeah that's one of the reasons I posted this branch now, so you could have a look and give some initial feedback, before I dived into seeing exactly what was going on in the tests.
> Incidentally, do you know any good image comparison tool, usable from the command line
I'm afraid not, looks like a niche that needs filling :P Oh, and thanks for your script, that'll be handy.
> I'm seeing spurious 2-tuples in some of the graphs
I'll have a look, but I'm doubting I can see much more than you did...
> I want the documentation text to match the images.
Agreed.
Stefano Rivera (stefanor) wrote : | # |
> I'd like Python 2.4/5 compatibility back pretty please :)
Restored in r97
> I'd rephrase rev 87 as...
OK, r98
> I avoided creating lists with all the items.
Yes, I can see that being an issue. One of the things I was concerned about. I've added a wrapper in r99. (This is where supporting Python 2 and 3 without 2to3 starts getting a little ugly, but I think it's worth it)
> '%-*s ...' % (width, foo, ...).
Nice, I didn't know about that trick. r100
> is a bug -- count should be an int there. I should add a unit test for that function!
Indeed, it'll catch people working on your code and only testing with unit tests :P Added a test in r102
> Incidentally, I've no conveniently prepackaged 3.2 for running the tests
Available in Debian experimental / Ubuntu natty. I'll go one step further and check for the existence of all pythons before running them. (I have no 2.4 on my machine) r101
> references.txt needs a fix: instead of using [range(7)] it should use [list(range(7))].
r103
Now to get those tests to pass...
- 93. By Stefano Rivera
-
Revert r83 (Help 2to3 to do the right thing with functions called filter) no longer necessary
- 94. By Stefano Rivera
-
List minor Python versions supported in classifiers
- 95. By Stefano Rivera
-
Provide next() compatibility function for tests
- 96. By Stefano Rivera
-
Support both im_{func,self} / __{func,self}__
- 97. By Stefano Rivera
-
Python 2.4, 2.5 support is restored
- 98. By Stefano Rivera
-
Style: Use try...except instead of version_info
- 99. By Stefano Rivera
-
Use a wrapper function for dict iteritems / items change
- 100. By Stefano Rivera
-
Use %-*s instead of ljust()
- 101. By Stefano Rivera
-
Test for the presence of all python versions before testing with them
- 102. By Stefano Rivera
-
Add test for show_most_
common_ types() - 103. By Stefano Rivera
-
Wrap range() in list() for py3k
- 104. By Stefano Rivera
-
Specify close_fds, to as default changes in Python 3.2, with accompanying warning
- 105. By Stefano Rivera
-
Deal with varying node numbers with a doctest option and an additional custom checker
Stefano Rivera (stefanor) wrote : | # |
> Now to get those tests to pass...
I'm happy. I've added 3.0 to the list of Pythons to test with, but not to the trove classifiers, as I don't have a 3.0 to test with.
Marius Gedminas (mgedmin) wrote : | # |
Excellent!
For the record, all tests pass on Python 2.4 on my machine.
I'm wondering about two little things:
- perhaps it would be better to use unbound dict methods (dict.items/
- for consistency, maybe it would be better to do
try:
except AttributeError:
# Python 3.x
iteritems = dict.items
else:
# Python 2.x
iteritems = dict.iteritems
instead of the only remaining sys.version check?
- perhaps # doctest: +NODES_VARY in almost every example clutter the documentation too much, and it would be simpler/better to just unconditionally turn that bit of normalization on?
Three little things, I'm worrying about three little things.
I'll come in again. :-)
Nevertheless, I'm prepared to merge the branch as is. Tomorrow, since it's 4 AM and I'm prone to mistakes when sleepy.
Stefano Rivera (stefanor) wrote : | # |
> perhaps it would be better to use unbound dict methods
Yeah, wasn't thinking about those
> NODES_VARY in almost every example
There were only 4 of them (example images containing stack frames), so I thought it was worth the check, but happy to drop it.
> Tomorrow, since it's 4 AM and I'm prone to mistakes when sleepy.
Good call, I make terrible mistakes when I approve merges late at night
Marius Gedminas (mgedmin) wrote : | # |
Of course it had to be 4:30 AM the next day before I found the time to do a merge :)
Marius Gedminas (mgedmin) wrote : | # |
And now I can't push to launchpad because I stupidly ran 'bzr upgrade' on my branch, so I have to go hunt the right web page and upgrade launchpad's copy, or slowly do it over 'bzr upgrade lp:objgraph'.
I think I'll try the slow way. At least it's simple.
Preview Diff
1 | === modified file 'Makefile' |
2 | --- Makefile 2010-12-08 22:06:31 +0000 |
3 | +++ Makefile 2010-12-16 13:53:08 +0000 |
4 | @@ -32,10 +32,16 @@ |
5 | |
6 | .PHONY: test-all-pythons |
7 | test-all-pythons: |
8 | - make test PYTHON=python2.4 |
9 | - make test PYTHON=python2.5 |
10 | - make test PYTHON=python2.6 |
11 | - make test PYTHON=python2.7 |
12 | + set -e; \ |
13 | + for ver in 2.4 2.5 2.6 2.7 3.0 3.1 3.2; do \ |
14 | + if which python$$ver > /dev/null; then \ |
15 | + $(MAKE) test PYTHON=python$$ver; \ |
16 | + else \ |
17 | + echo "=================================="; \ |
18 | + echo "Skipping python$$ver, not available."; \ |
19 | + echo "=================================="; \ |
20 | + fi; \ |
21 | + done |
22 | |
23 | .PHONY: preview-pypi-description |
24 | preview-pypi-description: |
25 | |
26 | === modified file 'generator-sample.txt' |
27 | --- generator-sample.txt 2010-12-05 15:46:33 +0000 |
28 | +++ generator-sample.txt 2010-12-16 13:53:08 +0000 |
29 | @@ -17,7 +17,7 @@ |
30 | and we make it active |
31 | |
32 | >>> it = count_to_three() |
33 | - >>> it.next() |
34 | + >>> next(it) |
35 | 1 |
36 | |
37 | Now we can see that our Canary object is alive in memory |
38 | @@ -30,7 +30,7 @@ |
39 | |
40 | >>> objgraph.show_backrefs(objgraph.by_type('Canary'), |
41 | ... max_depth=7, |
42 | - ... filename='canary.png') |
43 | + ... filename='canary.png') # doctest: +NODES_VARY |
44 | Graph written to ....dot (15 nodes) |
45 | Image generated as canary.png |
46 | |
47 | |
48 | === modified file 'index.txt' |
49 | --- index.txt 2010-12-08 22:53:30 +0000 |
50 | +++ index.txt 2010-12-16 13:53:08 +0000 |
51 | @@ -28,7 +28,7 @@ |
52 | |
53 | Now try |
54 | |
55 | - >>> objgraph.show_backrefs([x], filename='sample-backref-graph.png') |
56 | + >>> objgraph.show_backrefs([x], filename='sample-backref-graph.png') # doctest: +NODES_VARY |
57 | Graph written to ....dot (8 nodes) |
58 | Image generated as sample-backref-graph.png |
59 | |
60 | @@ -39,6 +39,19 @@ |
61 | :scale: 50% |
62 | |
63 | |
64 | +Memory overview |
65 | +--------------- |
66 | + |
67 | +To get a quick overview of the objects in memory |
68 | + |
69 | + >>> objgraph.show_most_common_types() # doctest: +RANDOM_OUTPUT |
70 | + tuple 5351 |
71 | + function 1369 |
72 | + wrapper_descriptor 967 |
73 | + dict 786 |
74 | + ... |
75 | + |
76 | + |
77 | Memory leak example |
78 | ------------------- |
79 | |
80 | |
81 | === modified file 'objgraph.py' |
82 | --- objgraph.py 2010-12-08 23:11:28 +0000 |
83 | +++ objgraph.py 2010-12-16 13:53:08 +0000 |
84 | @@ -52,6 +52,19 @@ |
85 | import itertools |
86 | |
87 | |
88 | +try: |
89 | + basestring |
90 | +except NameError: |
91 | + # Python 3.x compatibility |
92 | + basestring = str |
93 | + |
94 | +# Dictionary iteration behavior change in Python 3: |
95 | +if sys.version_info < (3,): |
96 | + iteritems = lambda x: x.iteritems() |
97 | +else: |
98 | + iteritems = lambda x: x.items() |
99 | + |
100 | + |
101 | def count(typename): |
102 | """Count objects tracked by the garbage collector with a given class name. |
103 | |
104 | @@ -132,7 +145,7 @@ |
105 | stats = most_common_types(limit) |
106 | width = max(len(name) for name, count in stats) |
107 | for name, count in stats: |
108 | - print name.ljust(width), count |
109 | + print('%-*s %i' % (width, name, count)) |
110 | |
111 | |
112 | def show_growth(limit=10, peak_stats={}): |
113 | @@ -160,7 +173,7 @@ |
114 | gc.collect() |
115 | stats = typestats() |
116 | deltas = {} |
117 | - for name, count in stats.iteritems(): |
118 | + for name, count in iteritems(stats): |
119 | old_count = peak_stats.get(name, 0) |
120 | if count > old_count: |
121 | deltas[name] = count - old_count |
122 | @@ -172,7 +185,7 @@ |
123 | if deltas: |
124 | width = max(len(name) for name, count in deltas) |
125 | for name, delta in deltas: |
126 | - print name.ljust(width), "%9d %+9d" % (stats[name], delta) |
127 | + print('%-*s%9d %+9d' % (width, name, stats[name], delta)) |
128 | |
129 | |
130 | def by_type(typename): |
131 | @@ -399,13 +412,13 @@ |
132 | if not isinstance(objs, (list, tuple)): |
133 | objs = [objs] |
134 | if filename and filename.endswith('.dot'): |
135 | - f = file(filename, 'w') |
136 | + f = open(filename, 'w') |
137 | dot_filename = filename |
138 | else: |
139 | fd, dot_filename = tempfile.mkstemp('.dot', text=True) |
140 | f = os.fdopen(fd, "w") |
141 | - print >> f, 'digraph ObjectGraph {' |
142 | - print >> f, ' node[shape=box, style=filled, fillcolor=white];' |
143 | + f.write('digraph ObjectGraph {\n' |
144 | + ' node[shape=box, style=filled, fillcolor=white];\n') |
145 | queue = [] |
146 | depth = {} |
147 | ignore = set(extra_ignore) |
148 | @@ -417,7 +430,7 @@ |
149 | ignore.add(id(sys._getframe())) # this function |
150 | ignore.add(id(sys._getframe(1))) # show_refs/show_backrefs, most likely |
151 | for obj in objs: |
152 | - print >> f, ' %s[fontcolor=red];' % (obj_node_id(obj)) |
153 | + f.write(' %s[fontcolor=red];\n' % (obj_node_id(obj))) |
154 | depth[id(obj)] = 0 |
155 | queue.append(obj) |
156 | del obj |
157 | @@ -427,7 +440,7 @@ |
158 | nodes += 1 |
159 | target = queue.pop(0) |
160 | tdepth = depth[id(target)] |
161 | - print >> f, ' %s[label="%s"];' % (obj_node_id(target), obj_label(target, extra_info, refcounts)) |
162 | + f.write(' %s[label="%s"];\n' % (obj_node_id(target), obj_label(target, extra_info, refcounts))) |
163 | h, s, v = gradient((0, 0, 1), (0, 0, .3), tdepth, max_depth) |
164 | if inspect.ismodule(target): |
165 | h = .3 |
166 | @@ -436,12 +449,12 @@ |
167 | h = .6 |
168 | s = .6 |
169 | v = 0.5 + v * 0.5 |
170 | - print >> f, ' %s[fillcolor="%g,%g,%g"];' % (obj_node_id(target), h, s, v) |
171 | + f.write(' %s[fillcolor="%g,%g,%g"];\n' % (obj_node_id(target), h, s, v)) |
172 | if v < 0.5: |
173 | - print >> f, ' %s[fontcolor=white];' % (obj_node_id(target)) |
174 | + f.write(' %s[fontcolor=white];\n' % (obj_node_id(target))) |
175 | if hasattr(getattr(target, '__class__', None), '__del__'): |
176 | - print >> f, " %s->%s_has_a_del[color=red,style=dotted,len=0.25,weight=10];" % (obj_node_id(target), obj_node_id(target)) |
177 | - print >> f, ' %s_has_a_del[label="__del__",shape=doublecircle,height=0.25,color=red,fillcolor="0,.5,1",fontsize=6];' % (obj_node_id(target)) |
178 | + f.write(" %s->%s_has_a_del[color=red,style=dotted,len=0.25,weight=10];\n" % (obj_node_id(target), obj_node_id(target))) |
179 | + f.write(' %s_has_a_del[label="__del__",shape=doublecircle,height=0.25,color=red,fillcolor="0,.5,1",fontsize=6];\n' % (obj_node_id(target))) |
180 | if tdepth >= max_depth: |
181 | continue |
182 | if inspect.ismodule(target) and not swap_source_target: |
183 | @@ -467,7 +480,7 @@ |
184 | else: |
185 | srcnode, tgtnode = source, target |
186 | elabel = edge_label(srcnode, tgtnode) |
187 | - print >> f, ' %s -> %s%s;' % (obj_node_id(srcnode), obj_node_id(tgtnode), elabel) |
188 | + f.write(' %s -> %s%s;\n' % (obj_node_id(srcnode), obj_node_id(tgtnode), elabel)) |
189 | if id(source) not in depth: |
190 | depth[id(source)] = tdepth + 1 |
191 | queue.append(source) |
192 | @@ -482,36 +495,36 @@ |
193 | else: |
194 | label = "%d more backreferences" % skipped |
195 | edge = "too_many_%s->%s" % (obj_node_id(target), obj_node_id(target)) |
196 | - print >> f, ' %s[color=red,style=dotted,len=0.25,weight=10];' % edge |
197 | - print >> f, ' too_many_%s[label="%s",shape=box,height=0.25,color=red,fillcolor="%g,%g,%g",fontsize=6];' % (obj_node_id(target), label, h, s, v) |
198 | - print >> f, ' too_many_%s[fontcolor=white];' % (obj_node_id(target)) |
199 | - print >> f, "}" |
200 | + f.write(' %s[color=red,style=dotted,len=0.25,weight=10];\n' % edge) |
201 | + f.write(' too_many_%s[label="%s",shape=box,height=0.25,color=red,fillcolor="%g,%g,%g",fontsize=6];\n' % (obj_node_id(target), label, h, s, v)) |
202 | + f.write(' too_many_%s[fontcolor=white];\n' % (obj_node_id(target))) |
203 | + f.write("}\n") |
204 | f.close() |
205 | - print "Graph written to %s (%d nodes)" % (dot_filename, nodes) |
206 | + print("Graph written to %s (%d nodes)" % (dot_filename, nodes)) |
207 | if not filename and program_in_path('xdot'): |
208 | - print "Spawning graph viewer (xdot)" |
209 | - subprocess.Popen(['xdot', dot_filename]) |
210 | + print("Spawning graph viewer (xdot)") |
211 | + subprocess.Popen(['xdot', dot_filename], close_fds=True) |
212 | elif program_in_path('dot'): |
213 | if not filename: |
214 | - print "Graph viewer (xdot) not found, generating a png instead" |
215 | + print("Graph viewer (xdot) not found, generating a png instead") |
216 | if filename and filename.endswith('.png'): |
217 | - f = file(filename, 'wb') |
218 | + f = open(filename, 'wb') |
219 | png_filename = filename |
220 | else: |
221 | if filename: |
222 | - print "Unrecognized file type (%s)" % filename |
223 | + print("Unrecognized file type (%s)" % filename) |
224 | fd, png_filename = tempfile.mkstemp('.png', text=False) |
225 | f = os.fdopen(fd, "wb") |
226 | dot = subprocess.Popen(['dot', '-Tpng', dot_filename], |
227 | - stdout=f) |
228 | + stdout=f, close_fds=False) |
229 | dot.wait() |
230 | f.close() |
231 | - print "Image generated as %s" % png_filename |
232 | + print("Image generated as %s" % png_filename) |
233 | else: |
234 | if filename: |
235 | - print "Graph viewer (xdot) and image renderer (dot) not found, not doing anything else" |
236 | + print("Graph viewer (xdot) and image renderer (dot) not found, not doing anything else") |
237 | else: |
238 | - print "Unrecognized file type (%s), not doing anything else" % filename |
239 | + print("Unrecognized file type (%s), not doing anything else" % filename) |
240 | |
241 | |
242 | def obj_node_id(obj): |
243 | @@ -551,10 +564,18 @@ |
244 | types.BuiltinFunctionType)): |
245 | return obj.__name__ |
246 | if isinstance(obj, types.MethodType): |
247 | - if obj.im_self is not None: |
248 | - return obj.im_func.__name__ + ' (bound)' |
249 | - else: |
250 | - return obj.im_func.__name__ |
251 | + try: |
252 | + if obj.__self__ is not None: |
253 | + return obj.__func__.__name__ + ' (bound)' |
254 | + else: |
255 | + return obj.__func__.__name__ |
256 | + except AttributeError: |
257 | + # Python < 2.6 compatibility |
258 | + if obj.im_self is not None: |
259 | + return obj.im_func.__name__ + ' (bound)' |
260 | + else: |
261 | + return obj.im_func.__name__ |
262 | + |
263 | if isinstance(obj, types.FrameType): |
264 | return '%s:%s' % (obj.f_code.co_filename, obj.f_lineno) |
265 | if isinstance(obj, (tuple, list, dict, set)): |
266 | @@ -586,12 +607,19 @@ |
267 | if target is source.f_globals: |
268 | return ' [label="f_globals",weight=10]' |
269 | if isinstance(source, types.MethodType): |
270 | - if target is source.im_self: |
271 | - return ' [label="im_self",weight=10]' |
272 | - if target is source.im_func: |
273 | - return ' [label="im_func",weight=10]' |
274 | + try: |
275 | + if target is source.__self__: |
276 | + return ' [label="__self__",weight=10]' |
277 | + if target is source.__func__: |
278 | + return ' [label="__func__",weight=10]' |
279 | + except AttributeError: |
280 | + # Python < 2.6 compatibility |
281 | + if target is source.im_self: |
282 | + return ' [label="im_self",weight=10]' |
283 | + if target is source.im_func: |
284 | + return ' [label="im_func",weight=10]' |
285 | if isinstance(source, dict): |
286 | - for k, v in source.iteritems(): |
287 | + for k, v in iteritems(source): |
288 | if v is target: |
289 | if isinstance(k, basestring) and k: |
290 | return ' [label="%s",weight=2]' % quote(k) |
291 | |
292 | === modified file 'references.txt' |
293 | --- references.txt 2010-12-05 14:53:52 +0000 |
294 | +++ references.txt 2010-12-16 13:53:08 +0000 |
295 | @@ -4,7 +4,7 @@ |
296 | Objects that have too many references are truncated |
297 | |
298 | >>> import objgraph |
299 | - >>> objgraph.show_refs([range(7)], too_many=5, filename='too-many.png') |
300 | + >>> objgraph.show_refs([list(range(7))], too_many=5, filename='too-many.png') |
301 | Graph written to ....dot (6 nodes) |
302 | Image generated as too-many.png |
303 | |
304 | @@ -32,7 +32,7 @@ |
305 | >>> import sys |
306 | >>> one_reference = object() |
307 | >>> objgraph.show_backrefs([one_reference], refcounts=True, |
308 | - ... filename='refcounts.png') |
309 | + ... filename='refcounts.png') # doctest: +NODES_VARY |
310 | Graph written to ....dot (5 nodes) |
311 | Image generated as refcounts.png |
312 | |
313 | |
314 | === modified file 'setup.py' |
315 | --- setup.py 2010-12-08 22:53:30 +0000 |
316 | +++ setup.py 2010-12-16 13:53:08 +0000 |
317 | @@ -1,5 +1,5 @@ |
318 | #!/usr/bin/python |
319 | -import os, sys, unittest, doctest |
320 | +import os, re, sys, unittest, doctest |
321 | |
322 | try: |
323 | from setuptools import setup |
324 | @@ -27,9 +27,11 @@ |
325 | |
326 | |
327 | def get_version(): |
328 | - d = {} |
329 | - exec read('objgraph.py') in d |
330 | - return d['__version__'] |
331 | + r = re.compile('^__version__ = "(.+)"$') |
332 | + for line in read('objgraph.py').splitlines(): |
333 | + m = r.match(line) |
334 | + if m: |
335 | + return m.group(1) |
336 | |
337 | |
338 | def get_description(): |
339 | @@ -43,7 +45,7 @@ |
340 | if not doctests: |
341 | doctests = tests.find_doctests() |
342 | suite = doctest.DocFileSuite(optionflags=doctest.ELLIPSIS, |
343 | - checker=tests.MyChecker(), |
344 | + checker=tests.RandomOutputChecker(), |
345 | *doctests) |
346 | result = unittest.TextTestRunner().run(suite) |
347 | if not result.wasSuccessful(): |
348 | @@ -63,4 +65,15 @@ |
349 | license='MIT', |
350 | description='Draws Python object reference graphs with graphviz', |
351 | long_description=get_description(), |
352 | + classifiers=[ |
353 | + 'Programming Language :: Python', |
354 | + 'Programming Language :: Python :: 2', |
355 | + 'Programming Language :: Python :: 2.4', |
356 | + 'Programming Language :: Python :: 2.5', |
357 | + 'Programming Language :: Python :: 2.6', |
358 | + 'Programming Language :: Python :: 2.7', |
359 | + 'Programming Language :: Python :: 3', |
360 | + 'Programming Language :: Python :: 3.1', |
361 | + 'Programming Language :: Python :: 3.2', |
362 | + ], |
363 | py_modules=['objgraph']) |
364 | |
365 | === modified file 'tests.py' |
366 | --- tests.py 2010-12-08 22:53:30 +0000 |
367 | +++ tests.py 2010-12-16 13:53:08 +0000 |
368 | @@ -1,16 +1,18 @@ |
369 | #!/usr/bin/python |
370 | -import unittest |
371 | import doctest |
372 | -import tempfile |
373 | +import glob |
374 | import os |
375 | +import re |
376 | import shutil |
377 | -import glob |
378 | - |
379 | - |
380 | +import tempfile |
381 | +import unittest |
382 | + |
383 | + |
384 | +NODES_VARY = doctest.register_optionflag('NODES_VARY') |
385 | RANDOM_OUTPUT = doctest.register_optionflag('RANDOM_OUTPUT') |
386 | |
387 | |
388 | -class MyChecker(doctest.OutputChecker): |
389 | +class RandomOutputChecker(doctest.OutputChecker): |
390 | |
391 | def check_output(self, want, got, optionflags): |
392 | if optionflags & RANDOM_OUTPUT: |
393 | @@ -18,12 +20,27 @@ |
394 | return doctest.OutputChecker.check_output(self, want, got, optionflags) |
395 | |
396 | |
397 | +class IgnoreNodeCountChecker(RandomOutputChecker): |
398 | + _r = re.compile('\(\d+ nodes\)$', re.MULTILINE) |
399 | + |
400 | + def check_output(self, want, got, optionflags): |
401 | + if optionflags & NODES_VARY: |
402 | + want = self._r.sub('(X nodes)', want) |
403 | + got = self._r.sub('(X nodes)', got) |
404 | + return RandomOutputChecker.check_output(self, want, got, optionflags) |
405 | + |
406 | + |
407 | def setUp(test): |
408 | test.tmpdir = tempfile.mkdtemp(prefix='test-objgraph-') |
409 | test.prevdir = os.getcwd() |
410 | test.prevtempdir = tempfile.tempdir |
411 | tempfile.tempdir = test.tmpdir |
412 | os.chdir(test.tmpdir) |
413 | + try: |
414 | + next |
415 | + except NameError: |
416 | + # Python < 2.6 compatibility |
417 | + test.globs['next'] = lambda it: it.next() |
418 | |
419 | |
420 | def tearDown(test): |
421 | @@ -41,7 +58,7 @@ |
422 | doctests = find_doctests() |
423 | return doctest.DocFileSuite(setUp=setUp, tearDown=tearDown, |
424 | optionflags=doctest.ELLIPSIS, |
425 | - checker=MyChecker(), |
426 | + checker=IgnoreNodeCountChecker(), |
427 | *doctests) |
428 | |
429 | if __name__ == '__main__': |
430 | |
431 | === modified file 'uncollectable.txt' |
432 | --- uncollectable.txt 2010-12-08 21:41:46 +0000 |
433 | +++ uncollectable.txt 2010-12-16 13:53:08 +0000 |
434 | @@ -27,7 +27,7 @@ |
435 | We highlight these objects by showing the existence of a ``__del__``. |
436 | |
437 | >>> objgraph.show_backrefs(objgraph.by_type('Nondestructible'), |
438 | - ... filename='finalizers.png') |
439 | + ... filename='finalizers.png') # doctest: +NODES_VARY |
440 | Graph written to ....dot (8 nodes) |
441 | Image generated as finalizers.png |
442 |
Overall impression: it's good, except I'd like Python 2.4/5 compatibility back pretty please :)
Revisions 83 and 85 could be merged today.
Is rev. 84 necessary? AFAIU you decided not to use 2to3 after all.
Rev 86: for Python 2.4 compatibility you could stuff next() into the global doctest namespace from
the tests.setUp(test):
try:
test.globs[ 'next'] = lambda it: it.__next__()
next
except NameError:
# Python 2.4/2.5 compatibility
so it doesn't clutter the doctest itself.
I'd rephrase rev 87 as
try:
basestring
except NameError:
# Python 3.x compatibility
basestring = str
purely because testing for features seems better to me than testing versions, but this is a minor thing.
Rev 89: hm, my gut feeling was that those can be large dicts, and so I avoided creating lists with all the items. But perhaps I'm wrong. How many different types can there be? I should go and check a couple of large applications.
That was for stats.items(); I'm less sanguine about source.items(). I'm especially worried that this will create new temporary references during graph traversal, which we may decide to follow... But I think you'd have noticed something like that.
Rev 90: I'm thinking about also changing
'%s ...' % (foo.ljust(width), ...)
with
'%-*s ...' % (width, foo, ...).
And I'm pretty sure
is a bug -- count should be an int there. I should add a unit test for that function! A simple smoke test would do, tagged with # doctest: +RANDOM_OUTPUT, like the existing show_growth() test in index.txt.
Rev 91: just how hard would it be to add support for Py2.4/2.5? Is it just next()/ __func_ _/__self_ _, or do more things break?
Incidentally, I've no conveniently prepackaged 3.2 for running the tests ... all the others are present. I'd like to change that makefile rule to skip 3.2 tests if python3.2 is not in $PATH.
Rev 92: please also add
Programming Language :: Python :: 2
otherwise it looks like objgraph only supports Python 3. While at it, why not add
Programming Language :: Python :: 2.4
Programming Language :: Python :: 2.5
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3.1
So, to summarize:
- Revert rev 84 common_ types
- Fix bug in show_most_
- Add Py 2.4/2.5 compat, reinstate their tests in make test-all-pythons
- Add more trove classifiers
Optionally:
- add quick smoke test for show_most_ common_ types
- rephrase basestring fallback
- rephrase use of str.ljust()
- skip python3.2 tests if it's not available
- investigate possible downsides of using items() instead of iteritems() on py2.x