From b5673a5176a561df34e066725bcfc9179d9d1522 Mon Sep 17 00:00:00 2001 From: Bryan Worrell Date: Sun, 7 Jun 2015 12:40:36 -0400 Subject: [PATCH] Added unit test for #254 and isolate() and isolation() decorators and context managers. --- cybox/test/__init__.py | 85 +++++++++++++++++++++ cybox/test/common/object_properties_test.py | 10 ++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/cybox/test/__init__.py b/cybox/test/__init__.py index 641221c9..3057f9ca 100644 --- a/cybox/test/__init__.py +++ b/cybox/test/__init__.py @@ -1,9 +1,12 @@ # Copyright (c) 2015, The MITRE Corporation. All rights reserved. # See LICENSE.txt for complete terms. +import sys import json import logging import unittest +import functools +import contextlib from mixbox.binding_utils import ExternalEncoding from mixbox.vendor import six @@ -14,6 +17,88 @@ logger = logging.getLogger(__name__) +@contextlib.contextmanager +def isolation(): + """Context manager that attempts to reset the sys.modules and globals() + to its initial state. + + This helps eliminate issues where tests pass because of residuals caused + by previous imports. + + Any code which leverages this context manager will need to re-import all + dependencies within. + """ + def del_sys_modules(to_del): + for mod in to_del: + del sys.modules[mod] + + def del_globals(to_del): + for g in globals_to_del: + del globals()[g] + + mods_snapshot = sys.modules.copy() + globals_snapshot = globals().copy() + + required_mods = ('unittest', 'logging', 'contextlib', 'json', 'six', + 'inspect', 'functools', 'sys') + + required_globals = ('__builtins__', '__name__', '__doc__', '__package__', + '__loader__', '__spec__') + + # Removing parent test packages will raise a warning when importing within + # the context manager. + test_mods = tuple(m for m in sys.modules if m.startswith('cybox.test')) + + # Modules we want to keep in the sys.modules + mods_to_keep = sys.builtin_module_names + test_mods + required_mods + + # Globals we want to keep in globals() + globals_to_keep = mods_to_keep + required_globals + + # Globals and modules we want to remove. + mods_to_del = (m for m in mods_snapshot if not m in mods_to_keep) + globals_to_del = (g for g in globals_snapshot if g not in globals_to_keep) + + try: + # Remove sys.modules and globals entries that are not required for + # the unit test. + del_sys_modules(mods_to_del) + del_globals(globals_to_del) + + # Return to caller + yield + + finally: + # Reset everything to the way it was. + mods_to_del = [m for m in sys.modules if m not in mods_snapshot] + globals_to_del = [g for g in globals() if g not in globals_snapshot] + + # Remove any imports that may have occurred inside the context manager. + del_sys_modules(mods_to_del) + del_globals(globals_to_del) + + # Reset everything to the way it was..sorta + sys.modules.update(mods_snapshot) + globals().update(globals_snapshot) + + +def isolate(func): + """Decorator which leverages the ``isolation`` contextmanager to make the + decorated function unaware of outside imports or globals. + + The globals() and sys.modules are returned to their previous state once + the decorated function exits. + + This helps test units that should fail but pass due to residuals from + previous imports. + """ + @functools.wraps(func) + def inner(*args, **kwargs): + with isolation(): + return func(*args, **kwargs) + return inner + + def assert_equal_ignore(item1, item2, ignore_keys=None): """Recursively compare two dictionaries, ignoring differences in some keys. """ diff --git a/cybox/test/common/object_properties_test.py b/cybox/test/common/object_properties_test.py index 48589843..6dd3b694 100644 --- a/cybox/test/common/object_properties_test.py +++ b/cybox/test/common/object_properties_test.py @@ -7,7 +7,7 @@ from cybox.common import ObjectProperties, Property from cybox.objects.address_object import Address -from cybox.test import EntityTestCase +from cybox.test import EntityTestCase, isolate class TestProperty(EntityTestCase, unittest.TestCase): @@ -48,5 +48,13 @@ def test_detect_address(self): self.assertTrue(isinstance(obj, Address)) + @isolate + def test_parent(self): + """Test that the ``parent`` property of an ObjectProperties object + does not raise an AttributeError. + """ + from cybox.objects.address_object import Address + self.assertTrue(bool(Address().parent.id_)) + if __name__ == "__main__": unittest.main()