Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

check.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. ##############################################################################
  2. #
  3. # Copyright (c) 2003 Zope Foundation and Contributors.
  4. # All Rights Reserved.
  5. #
  6. # This software is subject to the provisions of the Zope Public License,
  7. # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
  8. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
  9. # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  10. # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
  11. # FOR A PARTICULAR PURPOSE
  12. #
  13. ##############################################################################
  14. """
  15. Utilities for working with BTrees (TreeSets, Buckets, and Sets) at a low
  16. level.
  17. The primary function is check(btree), which performs value-based consistency
  18. checks of a kind btree._check() does not perform. See the function docstring
  19. for details.
  20. display(btree) displays the internal structure of a BTree (TreeSet, etc) to
  21. stdout.
  22. CAUTION: When a BTree node has only a single bucket child, it can be
  23. impossible to get at the bucket from Python code (__getstate__() may squash
  24. the bucket object out of existence, as a pickling storage optimization). In
  25. such a case, the code here synthesizes a temporary bucket with the same keys
  26. (and values, if the bucket is of a mapping type). This has no first-order
  27. consequences, but can mislead if you pay close attention to reported object
  28. addresses and/or object identity (the synthesized bucket has an address
  29. that doesn't exist in the actual BTree).
  30. """
  31. from BTrees.IFBTree import IFBTree, IFBucket, IFSet, IFTreeSet
  32. from BTrees.IFBTree import IFBTreePy, IFBucketPy, IFSetPy, IFTreeSetPy
  33. from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet
  34. from BTrees.IIBTree import IIBTreePy, IIBucketPy, IISetPy, IITreeSetPy
  35. from BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet
  36. from BTrees.IOBTree import IOBTreePy, IOBucketPy, IOSetPy, IOTreeSetPy
  37. from BTrees.LFBTree import LFBTree, LFBucket, LFSet, LFTreeSet
  38. from BTrees.LFBTree import LFBTreePy, LFBucketPy, LFSetPy, LFTreeSetPy
  39. from BTrees.LLBTree import LLBTree, LLBucket, LLSet, LLTreeSet
  40. from BTrees.LLBTree import LLBTreePy, LLBucketPy, LLSetPy, LLTreeSetPy
  41. from BTrees.LOBTree import LOBTree, LOBucket, LOSet, LOTreeSet
  42. from BTrees.LOBTree import LOBTreePy, LOBucketPy, LOSetPy, LOTreeSetPy
  43. from BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet
  44. from BTrees.OIBTree import OIBTreePy, OIBucketPy, OISetPy, OITreeSetPy
  45. from BTrees.OLBTree import OLBTree, OLBucket, OLSet, OLTreeSet
  46. from BTrees.OLBTree import OLBTreePy, OLBucketPy, OLSetPy, OLTreeSetPy
  47. from BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet
  48. from BTrees.OOBTree import OOBTreePy, OOBucketPy, OOSetPy, OOTreeSetPy
  49. from BTrees.utils import positive_id
  50. from BTrees.utils import oid_repr
  51. TYPE_UNKNOWN, TYPE_BTREE, TYPE_BUCKET = range(3)
  52. from ._compat import compare
  53. _type2kind = {}
  54. for kv in ('OO',
  55. 'II', 'IO', 'OI', 'IF',
  56. 'LL', 'LO', 'OL', 'LF',
  57. ):
  58. for name, kind in (
  59. ('BTree', (TYPE_BTREE, True)),
  60. ('Bucket', (TYPE_BUCKET, True)),
  61. ('TreeSet', (TYPE_BTREE, False)),
  62. ('Set', (TYPE_BUCKET, False)),
  63. ):
  64. _type2kind[globals()[kv+name]] = kind
  65. py = kv + name + 'Py'
  66. _type2kind[globals()[py]] = kind
  67. # Return pair
  68. #
  69. # TYPE_BTREE or TYPE_BUCKET, is_mapping
  70. def classify(obj):
  71. return _type2kind[type(obj)]
  72. BTREE_EMPTY, BTREE_ONE, BTREE_NORMAL = range(3)
  73. # If the BTree is empty, returns
  74. #
  75. # BTREE_EMPTY, [], []
  76. #
  77. # If the BTree has only one bucket, sometimes returns
  78. #
  79. # BTREE_ONE, bucket_state, None
  80. #
  81. # Else returns
  82. #
  83. # BTREE_NORMAL, list of keys, list of kids
  84. #
  85. # and the list of kids has one more entry than the list of keys.
  86. #
  87. # BTree.__getstate__() docs:
  88. #
  89. # For an empty BTree (self->len == 0), None.
  90. #
  91. # For a BTree with one child (self->len == 1), and that child is a bucket,
  92. # and that bucket has a NULL oid, a one-tuple containing a one-tuple
  93. # containing the bucket's state:
  94. #
  95. # (
  96. # (
  97. # child[0].__getstate__(),
  98. # ),
  99. # )
  100. #
  101. # Else a two-tuple. The first element is a tuple interleaving the BTree's
  102. # keys and direct children, of size 2*self->len - 1 (key[0] is unused and
  103. # is not saved). The second element is the firstbucket:
  104. #
  105. # (
  106. # (child[0], key[1], child[1], key[2], child[2], ...,
  107. # key[len-1], child[len-1]),
  108. # self->firstbucket
  109. # )
  110. _btree2bucket = {}
  111. for kv in ('OO',
  112. 'II', 'IO', 'OI', 'IF',
  113. 'LL', 'LO', 'OL', 'LF',
  114. ):
  115. _btree2bucket[globals()[kv+'BTree']] = globals()[kv+'Bucket']
  116. py = kv + 'BTreePy'
  117. _btree2bucket[globals()[py]] = globals()[kv+'BucketPy']
  118. _btree2bucket[globals()[kv+'TreeSet']] = globals()[kv+'Set']
  119. py = kv + 'TreeSetPy'
  120. _btree2bucket[globals()[kv+'TreeSetPy']] = globals()[kv+'SetPy']
  121. def crack_btree(t, is_mapping):
  122. state = t.__getstate__()
  123. if state is None:
  124. return BTREE_EMPTY, [], []
  125. assert isinstance(state, tuple)
  126. if len(state) == 1:
  127. state = state[0]
  128. assert isinstance(state, tuple) and len(state) == 1
  129. state = state[0]
  130. return BTREE_ONE, state, None
  131. assert len(state) == 2
  132. data, firstbucket = state
  133. n = len(data)
  134. assert n & 1
  135. kids = []
  136. keys = []
  137. i = 0
  138. for x in data:
  139. if i & 1:
  140. keys.append(x)
  141. else:
  142. kids.append(x)
  143. i += 1
  144. return BTREE_NORMAL, keys, kids
  145. # Returns
  146. #
  147. # keys, values # for a mapping; len(keys) == len(values) in this case
  148. # or
  149. # keys, [] # for a set
  150. #
  151. # bucket.__getstate__() docs:
  152. #
  153. # For a set bucket (self->values is NULL), a one-tuple or two-tuple. The
  154. # first element is a tuple of keys, of length self->len. The second element
  155. # is the next bucket, present if and only if next is non-NULL:
  156. #
  157. # (
  158. # (keys[0], keys[1], ..., keys[len-1]),
  159. # <self->next iff non-NULL>
  160. # )
  161. #
  162. # For a mapping bucket (self->values is not NULL), a one-tuple or two-tuple.
  163. # The first element is a tuple interleaving keys and values, of length
  164. # 2 * self->len. The second element is the next bucket, present iff next is
  165. # non-NULL:
  166. #
  167. # (
  168. # (keys[0], values[0], keys[1], values[1], ...,
  169. # keys[len-1], values[len-1]),
  170. # <self->next iff non-NULL>
  171. # )
  172. def crack_bucket(b, is_mapping):
  173. state = b.__getstate__()
  174. assert isinstance(state, tuple)
  175. assert 1 <= len(state) <= 2
  176. data = state[0]
  177. if not is_mapping:
  178. return data, []
  179. keys = []
  180. values = []
  181. n = len(data)
  182. assert n & 1 == 0
  183. i = 0
  184. for x in data:
  185. if i & 1:
  186. values.append(x)
  187. else:
  188. keys.append(x)
  189. i += 1
  190. return keys, values
  191. def type_and_adr(obj):
  192. if hasattr(obj, '_p_oid'):
  193. oid = oid_repr(obj._p_oid)
  194. else:
  195. oid = 'None'
  196. return "%s (0x%x oid=%s)" % (type(obj).__name__, positive_id(obj), oid)
  197. # Walker implements a depth-first search of a BTree (or TreeSet or Set or
  198. # Bucket). Subclasses must implement the visit_btree() and visit_bucket()
  199. # methods, and arrange to call the walk() method. walk() calls the
  200. # visit_XYZ() methods once for each node in the tree, in depth-first
  201. # left-to-right order.
  202. class Walker:
  203. def __init__(self, obj):
  204. self.obj = obj
  205. # obj is the BTree (BTree or TreeSet).
  206. # path is a list of indices, from the root. For example, if a BTree node
  207. # is child[5] of child[3] of the root BTree, [3, 5].
  208. # parent is the parent BTree object, or None if this is the root BTree.
  209. # is_mapping is True for a BTree and False for a TreeSet.
  210. # keys is a list of the BTree's internal keys.
  211. # kids is a list of the BTree's children.
  212. # If the BTree is an empty root node, keys == kids == [].
  213. # Else len(kids) == len(keys) + 1.
  214. # lo and hi are slice bounds on the values the elements of keys *should*
  215. # lie in (lo inclusive, hi exclusive). lo is None if there is no lower
  216. # bound known, and hi is None if no upper bound is known.
  217. def visit_btree(self, obj, path, parent, is_mapping,
  218. keys, kids, lo, hi):
  219. raise NotImplementedError
  220. # obj is the bucket (Bucket or Set).
  221. # path is a list of indices, from the root. For example, if a bucket
  222. # node is child[5] of child[3] of the root BTree, [3, 5].
  223. # parent is the parent BTree object.
  224. # is_mapping is True for a Bucket and False for a Set.
  225. # keys is a list of the bucket's keys.
  226. # values is a list of the bucket's values.
  227. # If is_mapping is false, values == []. Else len(keys) == len(values).
  228. # lo and hi are slice bounds on the values the elements of keys *should*
  229. # lie in (lo inclusive, hi exclusive). lo is None if there is no lower
  230. # bound known, and hi is None if no upper bound is known.
  231. def visit_bucket(self, obj, path, parent, is_mapping,
  232. keys, values, lo, hi):
  233. raise NotImplementedError
  234. def walk(self):
  235. obj = self.obj
  236. path = []
  237. stack = [(obj, path, None, None, None)]
  238. while stack:
  239. obj, path, parent, lo, hi = stack.pop()
  240. kind, is_mapping = classify(obj)
  241. if kind is TYPE_BTREE:
  242. bkind, keys, kids = crack_btree(obj, is_mapping)
  243. if bkind is BTREE_NORMAL:
  244. # push the kids, in reverse order (so they're popped off
  245. # the stack in forward order)
  246. n = len(kids)
  247. for i in range(len(kids)-1, -1, -1):
  248. newlo, newhi = lo, hi
  249. if i < n-1:
  250. newhi = keys[i]
  251. if i > 0:
  252. newlo = keys[i-1]
  253. stack.append((kids[i],
  254. path + [i],
  255. obj,
  256. newlo,
  257. newhi))
  258. elif bkind is BTREE_EMPTY:
  259. pass
  260. else:
  261. assert bkind is BTREE_ONE
  262. # Yuck. There isn't a bucket object to pass on, as
  263. # the bucket state is embedded directly in the BTree
  264. # state. Synthesize a bucket.
  265. assert kids is None # and "keys" is really the bucket
  266. # state
  267. bucket = _btree2bucket[type(obj)]()
  268. bucket.__setstate__(keys)
  269. stack.append((bucket,
  270. path + [0],
  271. obj,
  272. lo,
  273. hi))
  274. keys = []
  275. kids = [bucket]
  276. self.visit_btree(obj,
  277. path,
  278. parent,
  279. is_mapping,
  280. keys,
  281. kids,
  282. lo,
  283. hi)
  284. else:
  285. assert kind is TYPE_BUCKET
  286. keys, values = crack_bucket(obj, is_mapping)
  287. self.visit_bucket(obj,
  288. path,
  289. parent,
  290. is_mapping,
  291. keys,
  292. values,
  293. lo,
  294. hi)
  295. class Checker(Walker):
  296. def __init__(self, obj):
  297. Walker.__init__(self, obj)
  298. self.errors = []
  299. def check(self):
  300. self.walk()
  301. if self.errors:
  302. s = "Errors found in %s:" % type_and_adr(self.obj)
  303. self.errors.insert(0, s)
  304. s = "\n".join(self.errors)
  305. raise AssertionError(s)
  306. def visit_btree(self, obj, path, parent, is_mapping,
  307. keys, kids, lo, hi):
  308. self.check_sorted(obj, path, keys, lo, hi)
  309. def visit_bucket(self, obj, path, parent, is_mapping,
  310. keys, values, lo, hi):
  311. self.check_sorted(obj, path, keys, lo, hi)
  312. def check_sorted(self, obj, path, keys, lo, hi):
  313. i, n = 0, len(keys)
  314. for x in keys:
  315. # lo or hi are ommitted by supplying None. Thus the not
  316. # None checkes below.
  317. if lo is not None and not compare(lo, x) <= 0:
  318. s = "key %r < lower bound %r at index %d" % (x, lo, i)
  319. self.complain(s, obj, path)
  320. if hi is not None and not compare(x, hi) < 0:
  321. s = "key %r >= upper bound %r at index %d" % (x, hi, i)
  322. self.complain(s, obj, path)
  323. if i < n-1 and not compare(x, keys[i+1]) < 0:
  324. s = "key %r at index %d >= key %r at index %d" % (
  325. x, i, keys[i+1], i+1)
  326. self.complain(s, obj, path)
  327. i += 1
  328. def complain(self, msg, obj, path):
  329. s = "%s, in %s, path from root %s" % (
  330. msg,
  331. type_and_adr(obj),
  332. ".".join(map(str, path)))
  333. self.errors.append(s)
  334. class Printer(Walker): #pragma NO COVER
  335. def __init__(self, obj):
  336. Walker.__init__(self, obj)
  337. def display(self):
  338. self.walk()
  339. def visit_btree(self, obj, path, parent, is_mapping,
  340. keys, kids, lo, hi):
  341. indent = " " * len(path)
  342. print("%s%s %s with %d children" % (
  343. indent,
  344. ".".join(map(str, path)),
  345. type_and_adr(obj),
  346. len(kids)))
  347. indent += " "
  348. n = len(keys)
  349. for i in range(n):
  350. print("%skey %d: %r" % (indent, i, keys[i]))
  351. def visit_bucket(self, obj, path, parent, is_mapping,
  352. keys, values, lo, hi):
  353. indent = " " * len(path)
  354. print("%s%s %s with %d keys" % (
  355. indent,
  356. ".".join(map(str, path)),
  357. type_and_adr(obj),
  358. len(keys)))
  359. indent += " "
  360. n = len(keys)
  361. for i in range(n):
  362. print("%skey %d: %r" % (indent, i, keys[i]),)
  363. if is_mapping:
  364. print("value %r" % (values[i],))
  365. def check(btree):
  366. """Check internal value-based invariants in a BTree or TreeSet.
  367. The btree._check() method checks internal C-level pointer consistency.
  368. The check() function here checks value-based invariants: whether the
  369. keys in leaf bucket and internal nodes are in strictly increasing order,
  370. and whether they all lie in their expected range. The latter is a subtle
  371. invariant that can't be checked locally -- it requires propagating
  372. range info down from the root of the tree, and modifying it at each
  373. level for each child.
  374. Raises AssertionError if anything is wrong, with a string detail
  375. explaining the problems. The entire tree is checked before
  376. AssertionError is raised, and the string detail may be large (depending
  377. on how much went wrong).
  378. """
  379. Checker(btree).check()
  380. def display(btree): #pragma NO COVER
  381. "Display the internal structure of a BTree, Bucket, TreeSet or Set."
  382. Printer(btree).display()