Test method decorator to assert that no garbage has been created. Note that this decorator sets DEBUG_SAVEALL, which in some Python interpreters cannot be un-set (i.e. will disable garbage collection for any other unit tests in the same file/shard). Args: f: The function to decorate.
(f)
| 792 | |
| 793 | |
| 794 | def assert_no_garbage_created(f): |
| 795 | """Test method decorator to assert that no garbage has been created. |
| 796 | |
| 797 | Note that this decorator sets DEBUG_SAVEALL, which in some Python interpreters |
| 798 | cannot be un-set (i.e. will disable garbage collection for any other unit |
| 799 | tests in the same file/shard). |
| 800 | |
| 801 | Args: |
| 802 | f: The function to decorate. |
| 803 | |
| 804 | Returns: |
| 805 | The decorated function. |
| 806 | """ |
| 807 | |
| 808 | def decorator(self, **kwargs): |
| 809 | """Sets DEBUG_SAVEALL, runs the test, and checks for new garbage.""" |
| 810 | # Force-load `distribution_strategy_context` to prevent GC at |
| 811 | # test time when using eager. Remove once b/117329403 is resolved. |
| 812 | tape.distribution_strategy_context.get_strategy() |
| 813 | |
| 814 | gc.disable() |
| 815 | previous_debug_flags = gc.get_debug() |
| 816 | gc.set_debug(gc.DEBUG_SAVEALL) |
| 817 | gc.collect() |
| 818 | previous_garbage = len(gc.garbage) |
| 819 | result = f(self, **kwargs) |
| 820 | gc.collect() |
| 821 | new_garbage = len(gc.garbage) |
| 822 | if new_garbage > previous_garbage: |
| 823 | logging.error( |
| 824 | "The decorated test created work for Python's garbage collector, " |
| 825 | "likely due to a reference cycle. New objects in cycle(s):") |
| 826 | for i, obj in enumerate(gc.garbage[previous_garbage:]): |
| 827 | try: |
| 828 | logging.error("Object %d of %d", i, |
| 829 | len(gc.garbage) - previous_garbage) |
| 830 | |
| 831 | def _safe_object_str(obj): |
| 832 | return "<%s %d>" % (obj.__class__.__name__, id(obj)) |
| 833 | |
| 834 | logging.error(" Object type: %s", _safe_object_str(obj)) |
| 835 | logging.error( |
| 836 | " Referrer types: %s", ", ".join( |
| 837 | [_safe_object_str(ref) for ref in gc.get_referrers(obj)])) |
| 838 | logging.error( |
| 839 | " Referent types: %s", ", ".join( |
| 840 | [_safe_object_str(ref) for ref in gc.get_referents(obj)])) |
| 841 | logging.error(" Object attribute names: %s", dir(obj)) |
| 842 | logging.error(" Object __str__:") |
| 843 | logging.error(obj) |
| 844 | logging.error(" Object __repr__:") |
| 845 | logging.error(repr(obj)) |
| 846 | except Exception: # pylint: disable=broad-except |
| 847 | logging.error("(Exception while printing object)") |
| 848 | |
| 849 | # When garbage is created, this call can help identify reference cycles, |
| 850 | # which are typically the cause of such garbage. |
| 851 | if new_garbage > previous_garbage: |