Skip to content

Commit f530b80

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 f530b80

File tree

4 files changed

+61
-9
lines changed

4 files changed

+61
-9
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/test_toolchain.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33
import pytest
44
import mock
5+
from util import backup_attributes
56
from pythonforandroid.recipe import Recipe
67
from pythonforandroid.toolchain import ToolchainCL
78
from pythonforandroid.util import BuildInterruptingException
@@ -121,7 +122,10 @@ def test_recipes(self):
121122
Checks the `recipes` command prints out recipes information without crashing.
122123
"""
123124
argv = ['toolchain.py', 'recipes']
124-
with patch_sys_argv(argv), patch_sys_stdout() as m_stdout:
125+
with (
126+
patch_sys_argv(argv)), (
127+
patch_sys_stdout()) as m_stdout, (
128+
backup_attributes(Recipe, {'recipes'})):
125129
ToolchainCL()
126130
# check if we have common patterns in the output
127131
expected_strings = (
@@ -134,5 +138,3 @@ def test_recipes(self):
134138
)
135139
for expected_string in expected_strings:
136140
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
@@ -8,6 +8,7 @@
88
# `Python 2` or lower than `Python 3.3` does not
99
# have the `unittest.mock` module built-in
1010
import mock
11+
import util as test_util
1112
from pythonforandroid import util
1213

1314

@@ -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)