Skip to content

Commit e40a91c

Browse files
committed
Adds new backup_attributes context manager for tests
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)>
1 parent 3dabded commit e40a91c

File tree

5 files changed

+58
-8
lines changed

5 files changed

+58
-8
lines changed

pythonforandroid/toolchain.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -741,12 +741,12 @@ def _read_configuration():
741741
def recipes(self, args):
742742
"""
743743
Prints recipes basic info, e.g.
744-
```
745-
python3 3.7.1
746-
depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi']
747-
conflicts: ['python2']
748-
optional depends: ['sqlite3', 'libffi', 'openssl']
749-
```
744+
.. code-block:: bash
745+
746+
python3 3.7.1
747+
depends: ['hostpython3', 'sqlite3', 'openssl', 'libffi']
748+
conflicts: ['python2']
749+
optional depends: ['sqlite3', 'libffi', 'openssl']
750750
"""
751751
ctx = self.ctx
752752
if args.compact:

tests/__init__.py

Whitespace-only changes.

tests/test_toolchain.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33
import pytest
44
import mock
5+
from tests.util import backup_attributes
56
from pythonforandroid.recipe import Recipe
67
from pythonforandroid.toolchain import ToolchainCL
78
from pythonforandroid.util import BuildInterruptingException
@@ -115,6 +116,7 @@ def test_create_no_sdk_dir(self):
115116
assert ex_info.value.message == (
116117
'Android SDK dir was not specified, exiting.')
117118

119+
@backup_attributes(Recipe, {'recipes'})
118120
@pytest.mark.skipif(sys.version_info < (3, 0), reason="requires python3")
119121
def test_recipes(self):
120122
"""
@@ -134,5 +136,3 @@ def test_recipes(self):
134136
)
135137
for expected_string in expected_strings:
136138
assert expected_string in m_stdout.getvalue()
137-
# deletes static attribute to not mess with other tests
138-
del Recipe.recipes

tests/test_util.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# have the `unittest.mock` module built-in
1010
import mock
1111
from pythonforandroid import util
12+
from tests import util as test_util
1213

1314

1415
class TestUtil(unittest.TestCase):
@@ -17,6 +18,36 @@ class TestUtil(unittest.TestCase):
1718
:mod:`~pythonforandroid.util`.
1819
"""
1920

21+
def test_backup_attributes(self):
22+
"""
23+
Checks the helper backup_attributes context manager works as expected.
24+
"""
25+
class MyClass:
26+
def __init__(self, foo, bar):
27+
self.foo = foo
28+
self.bar = bar
29+
30+
# testing trivial flat backup
31+
foo = 'foo'
32+
bar = 'bar'
33+
my_object = MyClass(foo=foo, bar=bar)
34+
with test_util.backup_attributes(my_object, {'foo'}):
35+
my_object.foo = 'not foo'
36+
my_object.bar = 'not bar'
37+
assert my_object.foo == 'foo'
38+
assert my_object.bar == 'not bar'
39+
# testing deep backup
40+
foo = {'foo': {1, 2, 3}}
41+
bar = {'bar': {3, 2, 1}}
42+
my_object = MyClass(foo=foo, bar=bar)
43+
with test_util.backup_attributes(my_object, {'foo', 'bar'}):
44+
# changing the reference
45+
my_object.foo = {}
46+
# and mutating the object the attribute is referencing to
47+
my_object.bar['bar'] = None
48+
assert my_object.foo == {'foo': {1, 2, 3}}
49+
assert my_object.bar == {'bar': {3, 2, 1}}
50+
2051
@mock.patch("pythonforandroid.util.makedirs")
2152
def test_ensure_dir(self, mock_makedirs):
2253
"""

tests/util.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from copy import deepcopy
2+
from contextlib import contextmanager
3+
4+
5+
@contextmanager
6+
def backup_attributes(obj, attributes):
7+
"""
8+
Makes a backup of the object attributes that gets restored later.
9+
"""
10+
obj_dict = obj.__dict__
11+
# creates a subset dictionary of the attributes we're interested in
12+
attributes_backup = dict(
13+
(k, obj_dict[k]) for k in attributes if k in obj_dict)
14+
attributes_backup = deepcopy(attributes_backup)
15+
try:
16+
yield
17+
finally:
18+
for attribute in attributes:
19+
setattr(obj, attribute, attributes_backup[attribute])

0 commit comments

Comments
 (0)