Coverage for drivers/refcounter.py : 64%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1#!/usr/bin/python3
2#
3# Copyright (C) Citrix Systems Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published
7# by the Free Software Foundation; version 2.1 only.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17#
18# Persistent reference counter. This refcounter can maintain two separate
19# refcounts: one binary (which can have a value of 0 or 1) and one normal. The
20# parameter "binary" specifies which of the two counters to update, while the
21# return value is zero IFF both counters are zero
22#
23# Synchronization must be done at a higher level, by the users of this module
24#
26import os
27import util
28from lock import Lock
29import errno
32class RefCounterException(util.SMException):
33 pass
36class RefCounter:
37 """Persistent local-FS file-based reference counter. The
38 operations are get() and put(), and they are atomic."""
40 BASE_DIR = "/var/run/sm/refcount"
42 def get(obj, binary, ns=None):
43 """Get (inc ref count) 'obj' in namespace 'ns' (optional).
44 Returns new ref count"""
45 if binary:
46 return RefCounter._adjust(ns, obj, 0, 1)
47 else:
48 return RefCounter._adjust(ns, obj, 1, 0)
49 get = staticmethod(get)
51 def put(obj, binary, ns=None):
52 """Put (dec ref count) 'obj' in namespace 'ns' (optional). If ref
53 count was zero already, this operation is a no-op.
54 Returns new ref count"""
55 if binary:
56 return RefCounter._adjust(ns, obj, 0, -1)
57 else:
58 return RefCounter._adjust(ns, obj, -1, 0)
59 put = staticmethod(put)
61 def set(obj, count, binaryCount, ns=None):
62 """Set normal & binary counts explicitly to the specified values.
63 Returns new ref count"""
64 (obj, ns) = RefCounter._getSafeNames(obj, ns)
65 assert(count >= 0 and binaryCount >= 0)
66 if binaryCount > 1:
67 raise RefCounterException("Binary count = %d > 1" % binaryCount)
68 RefCounter._set(ns, obj, count, binaryCount)
69 set = staticmethod(set)
71 def check(obj, ns=None):
72 """Get the ref count values for 'obj' in namespace 'ns' (optional)"""
73 (obj, ns) = RefCounter._getSafeNames(obj, ns)
74 return RefCounter._get(ns, obj)
75 check = staticmethod(check)
77 def checkLocked(obj, ns):
78 """Lock-protected access"""
79 lock = Lock(obj, ns)
80 lock.acquire()
81 try:
82 return RefCounter.check(obj, ns)
83 finally:
84 lock.release()
85 checkLocked = staticmethod(checkLocked)
87 def reset(obj, ns=None):
88 """Reset ref counts for 'obj' in namespace 'ns' (optional) to 0."""
89 RefCounter.resetAll(ns, obj)
90 reset = staticmethod(reset)
92 def resetAll(ns=None, obj=None):
93 """Reset ref counts of 'obj' in namespace 'ns' to 0. If obj is not
94 provided, reset all existing objects in 'ns' to 0. If neither obj nor
95 ns are supplied, do this for all namespaces"""
96 if obj:
97 (obj, ns) = RefCounter._getSafeNames(obj, ns)
98 if ns:
99 nsList = [ns]
100 else:
101 if not util.pathexists(RefCounter.BASE_DIR):
102 return
103 try:
104 nsList = os.listdir(RefCounter.BASE_DIR)
105 except OSError:
106 raise RefCounterException("failed to get namespace list")
107 for ns in nsList:
108 RefCounter._reset(ns, obj)
109 resetAll = staticmethod(resetAll)
111 def _adjust(ns, obj, delta, binaryDelta):
112 """Add 'delta' to the normal refcount and 'binaryDelta' to the binary
113 refcount of 'obj' in namespace 'ns'.
114 Returns new ref count"""
115 if binaryDelta > 1 or binaryDelta < -1: 115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true
116 raise RefCounterException("Binary delta = %d outside [-1;1]" % \
117 binaryDelta)
118 (obj, ns) = RefCounter._getSafeNames(obj, ns)
119 (count, binaryCount) = RefCounter._get(ns, obj)
121 newCount = count + delta
122 newBinaryCount = binaryCount + binaryDelta
123 if newCount < 0:
124 util.SMlog("WARNING: decrementing normal refcount of 0")
125 newCount = 0
126 if newBinaryCount < 0:
127 util.SMlog("WARNING: decrementing binary refcount of 0")
128 newBinaryCount = 0
129 if newBinaryCount > 1:
130 newBinaryCount = 1
131 util.SMlog("Refcount for %s:%s (%d, %d) + (%d, %d) => (%d, %d)" % \
132 (ns, obj, count, binaryCount, delta, binaryDelta,
133 newCount, newBinaryCount))
134 RefCounter._set(ns, obj, newCount, newBinaryCount)
135 return newCount + newBinaryCount
136 _adjust = staticmethod(_adjust)
138 def _get(ns, obj):
139 """Get the ref count values for 'obj' in namespace 'ns'"""
140 objFile = os.path.join(RefCounter.BASE_DIR, ns, obj)
141 (count, binaryCount) = (0, 0)
142 if util.pathexists(objFile):
143 (count, binaryCount) = RefCounter._readCount(objFile)
144 return (count, binaryCount)
145 _get = staticmethod(_get)
147 def _set(ns, obj, count, binaryCount):
148 """Set the ref count values for 'obj' in namespace 'ns'"""
149 util.SMlog("Refcount for %s:%s set => (%d, %db)" % \
150 (ns, obj, count, binaryCount))
151 if count == 0 and binaryCount == 0:
152 RefCounter._removeObject(ns, obj)
153 else:
154 objFile = os.path.join(RefCounter.BASE_DIR, ns, obj)
156 while not RefCounter._writeCount(objFile, count, binaryCount):
157 RefCounter._createNamespace(ns)
159 _set = staticmethod(_set)
161 def _getSafeNames(obj, ns):
162 """Get a name that can be used as a file name"""
163 if not ns:
164 ns = obj.split('/')[0]
165 if not ns:
166 ns = "default"
167 for char in ['/', '*', '?', '\\']:
168 obj = obj.replace(char, "_")
169 return (obj, ns)
170 _getSafeNames = staticmethod(_getSafeNames)
172 def _createNamespace(ns):
173 nsDir = os.path.join(RefCounter.BASE_DIR, ns)
174 try:
175 os.makedirs(nsDir)
176 except OSError as e:
177 if e.errno != errno.EEXIST:
178 raise RefCounterException("failed to makedirs '%s' (%s)" % \
179 (nsDir, e))
180 _createNamespace = staticmethod(_createNamespace)
182 def _removeObject(ns, obj):
183 nsDir = os.path.join(RefCounter.BASE_DIR, ns)
184 objFile = os.path.join(nsDir, obj)
185 if not util.pathexists(objFile):
186 return
187 try:
188 os.unlink(objFile)
189 except OSError:
190 raise RefCounterException("failed to remove '%s'" % objFile)
192 try:
193 os.rmdir(nsDir)
194 except OSError as e:
195 namespaceAlreadyCleanedUp = e.errno == errno.ENOENT
196 newObjectAddedToNamespace = e.errno == errno.ENOTEMPTY
198 if namespaceAlreadyCleanedUp or newObjectAddedToNamespace: 198 ↛ 201line 198 didn't jump to line 201, because the condition on line 198 was never false
199 pass
200 else:
201 raise RefCounterException("failed to remove '%s'" % nsDir)
202 _removeObject = staticmethod(_removeObject)
204 def _reset(ns, obj=None):
205 nsDir = os.path.join(RefCounter.BASE_DIR, ns)
206 if not util.pathexists(nsDir):
207 return
208 if obj:
209 if not util.pathexists(os.path.join(nsDir, obj)): 209 ↛ 210line 209 didn't jump to line 210, because the condition on line 209 was never true
210 return
211 objList = [obj]
212 else:
213 try:
214 objList = os.listdir(nsDir)
215 except OSError:
216 raise RefCounterException("failed to list '%s'" % ns)
217 for obj in objList:
218 RefCounter._removeObject(ns, obj)
219 _reset = staticmethod(_reset)
221 def _readCount(fn):
222 try:
223 f = open(fn, 'r')
224 line = f.readline()
225 nums = line.split()
226 count = int(nums[0])
227 binaryCount = int(nums[1])
228 f.close()
229 except IOError:
230 raise RefCounterException("failed to read file '%s'" % fn)
231 return (count, binaryCount)
232 _readCount = staticmethod(_readCount)
234 def _writeCount(fn, count, binaryCount):
235 try:
236 f = open(fn, 'w')
237 f.write("%d %d\n" % (count, binaryCount))
238 f.close()
239 return True
240 except IOError as e:
241 fileNotFound = e.errno == errno.ENOENT
242 if fileNotFound: 242 ↛ 244line 242 didn't jump to line 244, because the condition on line 242 was never false
243 return False
244 raise RefCounterException("failed to write '(%d %d)' to '%s': %s" \
245 % (count, binaryCount, fn, e))
246 _writeCount = staticmethod(_writeCount)
248 def _runTests():
249 "Unit tests"
251 RefCounter.resetAll()
253 # A
254 (cnt, bcnt) = RefCounter.check("X", "A")
255 if cnt != 0 or bcnt != 0: 255 ↛ 256line 255 didn't jump to line 256, because the condition on line 255 was never true
256 print("Error: check = %d != 0 in the beginning" % cnt)
257 return -1
259 cnt = RefCounter.get("X", False, "A")
260 if cnt != 1: 260 ↛ 261line 260 didn't jump to line 261, because the condition on line 260 was never true
261 print("Error: count = %d != 1 after first get()" % cnt)
262 return -1
263 (cnt, bcnt) = RefCounter.check("X", "A")
264 if cnt != 1: 264 ↛ 265line 264 didn't jump to line 265, because the condition on line 264 was never true
265 print("Error: check = %d != 1 after first get()" % cnt)
266 return -1
268 cnt = RefCounter.put("X", False, "A")
269 if cnt != 0: 269 ↛ 270line 269 didn't jump to line 270, because the condition on line 269 was never true
270 print("Error: count = %d != 0 after get-put" % cnt)
271 return -1
272 (cnt, bcnt) = RefCounter.check("X", "A")
273 if cnt != 0: 273 ↛ 274line 273 didn't jump to line 274, because the condition on line 273 was never true
274 print("Error: check = %d != 0 after get-put" % cnt)
275 return -1
277 cnt = RefCounter.get("X", False, "A")
278 if cnt != 1: 278 ↛ 279line 278 didn't jump to line 279, because the condition on line 278 was never true
279 print("Error: count = %d != 1 after get-put-get" % cnt)
280 return -1
282 cnt = RefCounter.get("X", False, "A")
283 if cnt != 2: 283 ↛ 284line 283 didn't jump to line 284, because the condition on line 283 was never true
284 print("Error: count = %d != 2 after second get()" % cnt)
285 return -1
287 cnt = RefCounter.get("X", False, "A")
288 if cnt != 3: 288 ↛ 289line 288 didn't jump to line 289, because the condition on line 288 was never true
289 print("Error: count = %d != 3 after third get()" % cnt)
290 return -1
291 (cnt, bcnt) = RefCounter.check("X", "A")
292 if cnt != 3: 292 ↛ 293line 292 didn't jump to line 293, because the condition on line 292 was never true
293 print("Error: check = %d != 3 after third get()" % cnt)
294 return -1
296 cnt = RefCounter.put("Y", False, "A")
297 if cnt != 0: 297 ↛ 298line 297 didn't jump to line 298, because the condition on line 297 was never true
298 print("Error: count = %d != 0 after first put()" % cnt)
299 return -1
300 (cnt, bcnt) = RefCounter.check("Y", "A")
301 if cnt != 0: 301 ↛ 302line 301 didn't jump to line 302, because the condition on line 301 was never true
302 print("Error: check = %d != 0 after first put()" % cnt)
303 return -1
305 cnt = RefCounter.put("X", False, "A")
306 if cnt != 2: 306 ↛ 307line 306 didn't jump to line 307, because the condition on line 306 was never true
307 print("Error: count = %d != 2 after 3get-1put" % cnt)
308 return -1
310 cnt = RefCounter.put("X", False, "A")
311 if cnt != 1: 311 ↛ 312line 311 didn't jump to line 312, because the condition on line 311 was never true
312 print("Error: count = %d != 1 after 3get-2put" % cnt)
313 return -1
315 cnt = RefCounter.get("X", False, "A")
316 if cnt != 2: 316 ↛ 317line 316 didn't jump to line 317, because the condition on line 316 was never true
317 print("Error: count = %d != 2 after 4get-2put" % cnt)
318 return -1
319 (cnt, bcnt) = RefCounter.check("X", "A")
320 if cnt != 2: 320 ↛ 321line 320 didn't jump to line 321, because the condition on line 320 was never true
321 print("Error: check = %d != 2 after 4get-2put" % cnt)
322 return -1
324 cnt = RefCounter.put("X", False, "A")
325 if cnt != 1: 325 ↛ 326line 325 didn't jump to line 326, because the condition on line 325 was never true
326 print("Error: count = %d != 0 after 4get-3put" % cnt)
327 return -1
329 cnt = RefCounter.put("X", False, "A")
330 if cnt != 0: 330 ↛ 331line 330 didn't jump to line 331, because the condition on line 330 was never true
331 print("Error: count = %d != 0 after 4get-4put" % cnt)
332 return -1
333 (cnt, bcnt) = RefCounter.check("X", "A")
334 if cnt != 0: 334 ↛ 335line 334 didn't jump to line 335, because the condition on line 334 was never true
335 print("Error: check = %d != 0 after 4get-4put" % cnt)
336 return -1
338 # B
339 cnt = RefCounter.put("Z", False, "B")
340 if cnt != 0: 340 ↛ 341line 340 didn't jump to line 341, because the condition on line 340 was never true
341 print("Error: count = %d != 0 after new put()" % cnt)
342 return -1
344 cnt = RefCounter.get("Z", False, "B")
345 if cnt != 1: 345 ↛ 346line 345 didn't jump to line 346, because the condition on line 345 was never true
346 print("Error: count = %d != 1 after put-get" % cnt)
347 return -1
349 cnt = RefCounter.put("Z", False, "B")
350 if cnt != 0: 350 ↛ 351line 350 didn't jump to line 351, because the condition on line 350 was never true
351 print("Error: count = %d != 0 after put-get-put" % cnt)
352 return -1
353 (cnt, bcnt) = RefCounter.check("Z", "B")
354 if cnt != 0: 354 ↛ 355line 354 didn't jump to line 355, because the condition on line 354 was never true
355 print("Error: check = %d != 0 after put-get-put" % cnt)
356 return -1
358 cnt = RefCounter.get("Z", False, "B")
359 if cnt != 1: 359 ↛ 360line 359 didn't jump to line 360, because the condition on line 359 was never true
360 print("Error: count = %d != 1 after put-get-put-get" % cnt)
361 return -1
362 (cnt, bcnt) = RefCounter.check("Z", "B")
363 if cnt != 1: 363 ↛ 364line 363 didn't jump to line 364, because the condition on line 363 was never true
364 print("Error: check = %d != 1 after put-get-put-get" % cnt)
365 return -1
367 # set
368 (cnt, bcnt) = RefCounter.check("a/b")
369 if cnt != 0: 369 ↛ 370line 369 didn't jump to line 370, because the condition on line 369 was never true
370 print("Error: count = %d != 0 initially" % cnt)
371 return -1
372 RefCounter.set("a/b", 2, 0)
373 (cnt, bcnt) = RefCounter.check("a/b")
374 if cnt != 2 or bcnt != 0: 374 ↛ 375line 374 didn't jump to line 375, because the condition on line 374 was never true
375 print("Error: count = (%d,%d) != (2,0) after set(2,0)" % (cnt, bcnt))
376 return -1
377 cnt = RefCounter.put("a/b", False)
378 if cnt != 1: 378 ↛ 379line 378 didn't jump to line 379, because the condition on line 378 was never true
379 print("Error: count = %d != 1 after set(2)-put" % cnt)
380 return -1
381 cnt = RefCounter.get("a/b", False)
382 if cnt != 2: 382 ↛ 383line 382 didn't jump to line 383, because the condition on line 382 was never true
383 print("Error: count = %d != 2 after set(2)-put-get" % cnt)
384 return -1
385 RefCounter.set("a/b", 100, 0)
386 (cnt, bcnt) = RefCounter.check("a/b")
387 if cnt != 100 or bcnt != 0: 387 ↛ 388line 387 didn't jump to line 388, because the condition on line 387 was never true
388 print("Error: cnt,bcnt = (%d,%d) != (100,0) after set(100,0)" % \
389 (cnt, bcnt))
390 return -1
391 cnt = RefCounter.get("a/b", False)
392 if cnt != 101: 392 ↛ 393line 392 didn't jump to line 393, because the condition on line 392 was never true
393 print("Error: count = %d != 101 after get" % cnt)
394 return -1
395 RefCounter.set("a/b", 100, 1)
396 (cnt, bcnt) = RefCounter.check("a/b")
397 if cnt != 100 or bcnt != 1: 397 ↛ 398line 397 didn't jump to line 398, because the condition on line 397 was never true
398 print("Error: cnt,bcnt = (%d,%d) != (100,1) after set(100,1)" % \
399 (cnt, bcnt))
400 return -1
401 RefCounter.reset("a/b")
402 (cnt, bcnt) = RefCounter.check("a/b")
403 if cnt != 0: 403 ↛ 404line 403 didn't jump to line 404, because the condition on line 403 was never true
404 print("Error: check = %d != 0 after reset" % cnt)
405 return -1
407 # binary
408 cnt = RefCounter.get("A", True)
409 if cnt != 1: 409 ↛ 410line 409 didn't jump to line 410, because the condition on line 409 was never true
410 print("Error: count = %d != 1 after get(bin)" % cnt)
411 return -1
412 cnt = RefCounter.get("A", True)
413 if cnt != 1: 413 ↛ 414line 413 didn't jump to line 414, because the condition on line 413 was never true
414 print("Error: count = %d != 1 after get(bin)*2" % cnt)
415 return -1
416 cnt = RefCounter.put("A", True)
417 if cnt != 0: 417 ↛ 418line 417 didn't jump to line 418, because the condition on line 417 was never true
418 print("Error: count = %d != 0 after get(bin)*2-put(bin)" % cnt)
419 return -1
420 cnt = RefCounter.put("A", True)
421 if cnt != 0: 421 ↛ 422line 421 didn't jump to line 422, because the condition on line 421 was never true
422 print("Error: count = %d != 0 after get(bin)*2-put(bin)*2" % cnt)
423 return -1
424 try:
425 RefCounter.set("A", 0, 2)
426 print("Error: set(0,2) was allowed")
427 return -1
428 except RefCounterException:
429 pass
430 cnt = RefCounter.get("A", True)
431 if cnt != 1: 431 ↛ 432line 431 didn't jump to line 432, because the condition on line 431 was never true
432 print("Error: count = %d != 1 after get(bin)" % cnt)
433 return -1
434 cnt = RefCounter.get("A", False)
435 if cnt != 2: 435 ↛ 436line 435 didn't jump to line 436, because the condition on line 435 was never true
436 print("Error: count = %d != 2 after get(bin)-get" % cnt)
437 return -1
438 cnt = RefCounter.get("A", False)
439 if cnt != 3: 439 ↛ 440line 439 didn't jump to line 440, because the condition on line 439 was never true
440 print("Error: count = %d != 3 after get(bin)-get-get" % cnt)
441 return -1
442 cnt = RefCounter.get("A", True)
443 if cnt != 3: 443 ↛ 444line 443 didn't jump to line 444, because the condition on line 443 was never true
444 print("Error: count = %d != 3 after get(bin)-get*2-get(bin)" % cnt)
445 return -1
446 cnt = RefCounter.put("A", False)
447 if cnt != 2: 447 ↛ 448line 447 didn't jump to line 448, because the condition on line 447 was never true
448 print("Error: count = %d != 2 after get(bin)*2-get*2-put" % cnt)
449 return -1
450 cnt = RefCounter.put("A", True)
451 if cnt != 1: 451 ↛ 452line 451 didn't jump to line 452, because the condition on line 451 was never true
452 print("Error: cnt = %d != 1 after get(b)*2-get*2-put-put(b)" % cnt)
453 return -1
454 cnt = RefCounter.put("A", False)
455 if cnt != 0: 455 ↛ 456line 455 didn't jump to line 456, because the condition on line 455 was never true
456 print("Error: cnt = %d != 0 after get(b)*2-get*2-put*2-put(b)" % cnt)
457 return -1
459 # names
460 cnt = RefCounter.get("Z", False)
461 if cnt != 1: 461 ↛ 462line 461 didn't jump to line 462, because the condition on line 461 was never true
462 print("Error: count = %d != 1 after get (no ns 1)" % cnt)
463 return -1
465 cnt = RefCounter.get("Z/", False)
466 if cnt != 1: 466 ↛ 467line 466 didn't jump to line 467, because the condition on line 466 was never true
467 print("Error: count = %d != 1 after get (no ns 2)" % cnt)
468 return -1
470 cnt = RefCounter.get("/Z", False)
471 if cnt != 1: 471 ↛ 472line 471 didn't jump to line 472, because the condition on line 471 was never true
472 print("Error: count = %d != 1 after get (no ns 3)" % cnt)
473 return -1
475 cnt = RefCounter.get("/Z/*/?/\\", False)
476 if cnt != 1: 476 ↛ 477line 476 didn't jump to line 477, because the condition on line 476 was never true
477 print("Error: count = %d != 1 after get (no ns 4)" % cnt)
478 return -1
480 cnt = RefCounter.get("Z", False)
481 if cnt != 2: 481 ↛ 482line 481 didn't jump to line 482, because the condition on line 481 was never true
482 print("Error: count = %d != 2 after get (no ns 1)" % cnt)
483 return -1
485 cnt = RefCounter.get("Z/", False)
486 if cnt != 2: 486 ↛ 487line 486 didn't jump to line 487, because the condition on line 486 was never true
487 print("Error: count = %d != 2 after get (no ns 2)" % cnt)
488 return -1
490 cnt = RefCounter.get("/Z", False)
491 if cnt != 2: 491 ↛ 492line 491 didn't jump to line 492, because the condition on line 491 was never true
492 print("Error: count = %d != 2 after get (no ns 3)" % cnt)
493 return -1
495 cnt = RefCounter.get("/Z/*/?/\\", False)
496 if cnt != 2: 496 ↛ 497line 496 didn't jump to line 497, because the condition on line 496 was never true
497 print("Error: count = %d != 2 after get (no ns 4)" % cnt)
498 return -1
500 # resetAll
501 RefCounter.resetAll("B")
502 cnt = RefCounter.get("Z", False, "B")
503 if cnt != 1: 503 ↛ 504line 503 didn't jump to line 504, because the condition on line 503 was never true
504 print("Error: count = %d != 1 after resetAll-get" % cnt)
505 return -1
507 cnt = RefCounter.get("Z", False, "C")
508 if cnt != 1: 508 ↛ 509line 508 didn't jump to line 509, because the condition on line 508 was never true
509 print("Error: count = %d != 1 after C.get" % cnt)
510 return -1
512 RefCounter.resetAll("B")
513 cnt = RefCounter.get("Z", False, "B")
514 if cnt != 1: 514 ↛ 515line 514 didn't jump to line 515, because the condition on line 514 was never true
515 print("Error: count = %d != 1 after second resetAll-get" % cnt)
516 return -1
518 cnt = RefCounter.get("Z", False, "C")
519 if cnt != 2: 519 ↛ 520line 519 didn't jump to line 520, because the condition on line 519 was never true
520 print("Error: count = %d != 2 after second C.get" % cnt)
521 return -1
523 RefCounter.resetAll("D")
524 RefCounter.resetAll()
525 cnt = RefCounter.put("Z", False, "B")
526 if cnt != 0: 526 ↛ 527line 526 didn't jump to line 527, because the condition on line 526 was never true
527 print("Error: count = %d != 0 after resetAll-put" % cnt)
528 return -1
530 cnt = RefCounter.put("Z", False, "C")
531 if cnt != 0: 531 ↛ 532line 531 didn't jump to line 532, because the condition on line 531 was never true
532 print("Error: count = %d != 0 after C.resetAll-put" % cnt)
533 return -1
535 RefCounter.resetAll()
537 return 0
538 _runTests = staticmethod(_runTests)
541if __name__ == '__main__': 541 ↛ 542line 541 didn't jump to line 542, because the condition on line 541 was never true
542 print("Running unit tests...")
543 try:
544 if RefCounter._runTests() == 0:
545 print("All done, no errors")
546 except RefCounterException as e:
547 print("FAIL: Got exception: %s" % e)
548 raise