__init__.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # -*- coding: utf-8 -*-
  2. """
  3. Tests for greenlet.
  4. """
  5. from __future__ import absolute_import
  6. from __future__ import division
  7. from __future__ import print_function
  8. import unittest
  9. from gc import collect
  10. from gc import get_objects
  11. from threading import active_count as active_thread_count
  12. from time import sleep
  13. from time import time
  14. from greenlet import greenlet as RawGreenlet
  15. from greenlet import getcurrent
  16. from greenlet._greenlet import get_pending_cleanup_count
  17. from greenlet._greenlet import get_total_main_greenlets
  18. from . import leakcheck
  19. class TestCaseMetaClass(type):
  20. # wrap each test method with
  21. # a) leak checks
  22. def __new__(cls, classname, bases, classDict):
  23. # pylint and pep8 fight over what this should be called (mcs or cls).
  24. # pylint gets it right, but we can't scope disable pep8, so we go with
  25. # its convention.
  26. # pylint: disable=bad-mcs-classmethod-argument
  27. check_totalrefcount = True
  28. # Python 3: must copy, we mutate the classDict. Interestingly enough,
  29. # it doesn't actually error out, but under 3.6 we wind up wrapping
  30. # and re-wrapping the same items over and over and over.
  31. for key, value in list(classDict.items()):
  32. if key.startswith('test') and callable(value):
  33. classDict.pop(key)
  34. if check_totalrefcount:
  35. value = leakcheck.wrap_refcount(value)
  36. classDict[key] = value
  37. return type.__new__(cls, classname, bases, classDict)
  38. class TestCase(TestCaseMetaClass(
  39. "NewBase",
  40. (unittest.TestCase,),
  41. {})):
  42. cleanup_attempt_sleep_duration = 0.001
  43. cleanup_max_sleep_seconds = 1
  44. def wait_for_pending_cleanups(self,
  45. initial_active_threads=None,
  46. initial_main_greenlets=None):
  47. initial_active_threads = initial_active_threads or self.threads_before_test
  48. initial_main_greenlets = initial_main_greenlets or self.main_greenlets_before_test
  49. sleep_time = self.cleanup_attempt_sleep_duration
  50. # NOTE: This is racy! A Python-level thread object may be dead
  51. # and gone, but the C thread may not yet have fired its
  52. # destructors and added to the queue. There's no particular
  53. # way to know that's about to happen. We try to watch the
  54. # Python threads to make sure they, at least, have gone away.
  55. # Counting the main greenlets, which we can easily do deterministically,
  56. # also helps.
  57. # Always sleep at least once to let other threads run
  58. sleep(sleep_time)
  59. quit_after = time() + self.cleanup_max_sleep_seconds
  60. # TODO: We could add an API that calls us back when a particular main greenlet is deleted?
  61. # It would have to drop the GIL
  62. while (
  63. get_pending_cleanup_count()
  64. or active_thread_count() > initial_active_threads
  65. or (not self.expect_greenlet_leak
  66. and get_total_main_greenlets() > initial_main_greenlets)):
  67. sleep(sleep_time)
  68. if time() > quit_after:
  69. print("Time limit exceeded.")
  70. print("Threads: Waiting for only", initial_active_threads,
  71. "-->", active_thread_count())
  72. print("MGlets : Waiting for only", initial_main_greenlets,
  73. "-->", get_total_main_greenlets())
  74. break
  75. collect()
  76. def count_objects(self, kind=list, exact_kind=True):
  77. # pylint:disable=unidiomatic-typecheck
  78. # Collect the garbage.
  79. for _ in range(3):
  80. collect()
  81. if exact_kind:
  82. return sum(
  83. 1
  84. for x in get_objects()
  85. if type(x) is kind
  86. )
  87. # instances
  88. return sum(
  89. 1
  90. for x in get_objects()
  91. if isinstance(x, kind)
  92. )
  93. greenlets_before_test = 0
  94. threads_before_test = 0
  95. main_greenlets_before_test = 0
  96. expect_greenlet_leak = False
  97. def count_greenlets(self):
  98. """
  99. Find all the greenlets and subclasses tracked by the GC.
  100. """
  101. return self.count_objects(RawGreenlet, False)
  102. def setUp(self):
  103. # Ensure the main greenlet exists, otherwise the first test
  104. # gets a false positive leak
  105. super(TestCase, self).setUp()
  106. getcurrent()
  107. self.threads_before_test = active_thread_count()
  108. self.main_greenlets_before_test = get_total_main_greenlets()
  109. self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test)
  110. self.greenlets_before_test = self.count_greenlets()
  111. def tearDown(self):
  112. if getattr(self, 'skipTearDown', False):
  113. return
  114. self.wait_for_pending_cleanups(self.threads_before_test, self.main_greenlets_before_test)
  115. super(TestCase, self).tearDown()