Skip to content

Commit

Permalink
Adds new backup_attributes context manager for tests
Browse files Browse the repository at this point in the history
This context manager will makes it possible to rollback an object state
after leaving the context manager.
This is a follow up of
<kivy#1867 (comment)>

Also address docstring format as suggested by @opacam in
<kivy#1933 (comment)>
  • Loading branch information
AndreMiras committed Jul 31, 2019
1 parent 3dabded commit a37cf36
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 8 deletions.
12 changes: 6 additions & 6 deletions pythonforandroid/toolchain.py
Expand Up @@ -741,12 +741,12 @@ def _read_configuration():
def recipes(self, args):
"""
Prints recipes basic info, e.g.
```
python3 3.7.1
depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi']
conflicts: ['python2']
optional depends: ['sqlite3', 'libffi', 'openssl']
```
.. code-block:: bash
python3 3.7.1
depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi']
conflicts: ['python2']
optional depends: ['sqlite3', 'libffi', 'openssl']
"""
ctx = self.ctx
if args.compact:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_toolchain.py
Expand Up @@ -2,6 +2,7 @@
import sys
import pytest
import mock
from tests.util import backup_attributes
from pythonforandroid.recipe import Recipe
from pythonforandroid.toolchain import ToolchainCL
from pythonforandroid.util import BuildInterruptingException
Expand Down Expand Up @@ -115,6 +116,7 @@ def test_create_no_sdk_dir(self):
assert ex_info.value.message == (
'Android SDK dir was not specified, exiting.')

@backup_attributes(Recipe, {'recipes'})
@pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3")
def test_recipes(self):
"""
Expand All @@ -134,5 +136,3 @@ def test_recipes(self):
)
for expected_string in expected_strings:
assert expected_string in m_stdout.getvalue()
# deletes static attribute to not mess with other tests
del Recipe.recipes
31 changes: 31 additions & 0 deletions tests/test_util.py
Expand Up @@ -9,6 +9,7 @@
# have the `unittest.mock` module built-in
import mock
from pythonforandroid import util
from tests import util as test_util


class TestUtil(unittest.TestCase):
Expand All @@ -17,6 +18,36 @@ class TestUtil(unittest.TestCase):
:mod:`~pythonforandroid.util`.
"""

def test_backup_attributes(self):
"""
Checks the helper backup_attributes context manager works as expected.
"""
class MyClass:
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar

# testing trivial flat backup
foo = 'foo'
bar = 'bar'
my_object = MyClass(foo=foo, bar=bar)
with test_util.backup_attributes(my_object, {'foo'}):
my_object.foo = 'not foo'
my_object.bar = 'not bar'
assert my_object.foo == 'foo'
assert my_object.bar == 'not bar'
# testing deep backup
foo = {'foo': {1, 2, 3}}
bar = {'bar': {3, 2, 1}}
my_object = MyClass(foo=foo, bar=bar)
with test_util.backup_attributes(my_object, {'foo', 'bar'}):
# changing the reference
my_object.foo = {}
# and mutating the object the attribute is referencing to
my_object.bar['bar'] = None
assert my_object.foo == {'foo': {1, 2, 3}}
assert my_object.bar == {'bar': {3, 2, 1}}

@mock.patch("pythonforandroid.util.makedirs")
def test_ensure_dir(self, mock_makedirs):
"""
Expand Down
19 changes: 19 additions & 0 deletions tests/util.py
@@ -0,0 +1,19 @@
from copy import deepcopy
from contextlib import contextmanager


@contextmanager
def backup_attributes(obj, attributes):
"""
Makes a backup of the object attributes that gets restored later.
"""
obj_dict = obj.__dict__
# creates a subset dictionary of the attributes we're interested in
attributes_backup = dict(
(k, obj_dict[k]) for k in attributes if k in obj_dict)
attributes_backup = deepcopy(attributes_backup)
try:
yield
finally:
for attribute in attributes:
setattr(obj, attribute, attributes_backup[attribute])

0 comments on commit a37cf36

Please sign in to comment.