Merge lp:~elopio/u1-test-utils/logger_decorator into lp:u1-test-utils

Proposed by Leo Arias
Status: Merged
Approved by: Natalia Bidart
Approved revision: 55
Merged at revision: 45
Proposed branch: lp:~elopio/u1-test-utils/logger_decorator
Merge into: lp:u1-test-utils
Prerequisite: lp:~elopio/u1-test-utils/assert_page_path
Diff against target: 221 lines (+99/-1)
3 files modified
u1testutils/sst/__init__.py (+24/-0)
u1testutils/sst/selftests/unit/test_pages.py (+60/-1)
u1testutils/sst/sso/pages.py (+15/-0)
To merge this branch: bzr merge lp:~elopio/u1-test-utils/logger_decorator
Reviewer Review Type Date Requested Status
Natalia Bidart (community) Approve
Review via email: mp+157925@code.launchpad.net

Commit message

Added the actions logger decorator for the page objects.

To post a comment you must log in.
48. By Leo Arias

Added the decorator to the SSO pages.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks good. About the decorator, instead of

11 + def log_and_call(*args, **kwargs):
12 + class_name = str(args[0].__class__.__name__)

I would suggest using this approach, which is more explicit:

11 + def log_and_call(instance, *args, **kwargs):
12 + class_name = str(instance.__class__.__name__)
...
25 + return method(instance, *args, **kwargs)

Futhermore, I would suggest adding a decorator that will receive the logging function as a param, thus allowing the caller to log using different levels. For example:

http://bazaar.launchpad.net/~pycassos/pycasa/trunk/view/head:/pycasa/utils.py#L120

Nitpicking detail: there should be an empty blank line between the import <module> block and the from <module> import <feature> block.

review: Needs Fixing
49. By Leo Arias

Improved the logger using a more explicit approach with the instance as parameter.

50. By Leo Arias

Improve the logger decorator to receive the logging funciton.

51. By Leo Arias

Removed the log_func default.

52. By Leo Arias

Remove the unnecessary test.

53. By Leo Arias

Fixed lint.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

I made some tweaks to this logging function and pasted them here:

https://pastebin.canonical.com/89011/

I added comments inline explaining the changes, but will duplicate those here for the sake of the review:

"Creating several strings depending on conditions is expensive.
Also, is better to have non-chaging log messages to be able to
grep for specific keywords when debugging, so, I prefer to always
log the args and kwargs even if they are not set, since the fact
that no args nor kwargs were given is an important info to log.
Is particularly important to use %r and not %s.

You should not format the string to log outside the logging
function, for mainly 2 reasons: the formatting may not be needed
since the currently set log level may be higher than the level of
log_func, and because there may be a formatting error that could
raise an exception making the whole call to 'f' fail (and we want
to avoid that)."

review: Needs Fixing
54. By Leo Arias

Implemented nessita's suggestion for the log line.

55. By Leo Arias

Empty line between import forms.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks great! Thanks a lot.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'u1testutils/sst/__init__.py'
--- u1testutils/sst/__init__.py 2013-04-10 19:25:21 +0000
+++ u1testutils/sst/__init__.py 2013-04-12 15:18:27 +0000
@@ -18,10 +18,34 @@
1818
19import sst.actions19import sst.actions
2020
21from functools import wraps
22
2123
22logger = logging.getLogger('User test')24logger = logging.getLogger('User test')
2325
2426
27def log_action(log_func):
28 """Decorator to log the call of an action method."""
29
30 def middle(f):
31
32 @wraps(f)
33 def inner(instance, *args, **kwargs):
34 class_name = str(instance.__class__.__name__)
35 docstring = f.__doc__
36 if docstring:
37 docstring = docstring.split('\n')[0].strip()
38 else:
39 docstring = f.__name__
40 log_line = '%r: %r. Arguments %r. Keyword arguments: %r.'
41 log_func(log_line, class_name, docstring, args, kwargs)
42 return f(instance, *args, **kwargs)
43
44 return inner
45
46 return middle
47
48
25class Page(object):49class Page(object):
26 """Base class for the page objects used in acceptance testing.50 """Base class for the page objects used in acceptance testing.
2751
2852
=== modified file 'u1testutils/sst/selftests/unit/test_pages.py'
--- u1testutils/sst/selftests/unit/test_pages.py 2013-04-08 18:43:37 +0000
+++ u1testutils/sst/selftests/unit/test_pages.py 2013-04-12 15:18:27 +0000
@@ -12,10 +12,13 @@
12# You should have received a copy of the GNU General Public License along12# You should have received a copy of the GNU General Public License along
13# with this program. If not, see <http://www.gnu.org/licenses/>.13# with this program. If not, see <http://www.gnu.org/licenses/>.
1414
15import logging
15import mock16import mock
16import testtools17import testtools
1718
18from u1testutils.sst import Page19import u1testutils.logging
20
21from u1testutils.sst import log_action, Page
1922
2023
21class PageTestCase(testtools.TestCase):24class PageTestCase(testtools.TestCase):
@@ -58,3 +61,59 @@
58 page.headings2 = []61 page.headings2 = []
59 page.assert_page_is_open()62 page.assert_page_is_open()
60 assert not mock_assert.called63 assert not mock_assert.called
64
65
66class PageWithLogDecorator(Page):
67
68 @log_action(logging.info)
69 def do_something_without_docstring(self, *args, **kwargs):
70 pass
71
72 @log_action(logging.info)
73 def do_something_with_docstring(self, *args, **kwargs):
74 """Do something with docstring."""
75 pass
76
77 @log_action(logging.info)
78 def do_something_with_multiline_docstring(self, *args, **kwargs):
79 """Do something with a multiline docstring.
80
81 This should not be logged.
82 """
83 pass
84
85
86class PageLoggingTestCase(u1testutils.logging.LogHandlerTestCase):
87
88 def setUp(self):
89 super(PageLoggingTestCase, self).setUp()
90 self.root_logger.setLevel(logging.INFO)
91 self.page = PageWithLogDecorator(check=False)
92
93 def test_logged_action_without_docstring(self):
94 self.page.do_something_without_docstring(
95 'arg1', 'arg2', arg3='arg3', arg4='arg4')
96 self.assertLogLevelContains(
97 'INFO',
98 "'PageWithLogDecorator': 'do_something_without_docstring'. "
99 "Arguments ('arg1', 'arg2'). "
100 "Keyword arguments: {'arg3': 'arg3', 'arg4': 'arg4'}.")
101
102 def test_logged_action_with_docstring(self):
103 self.page.do_something_with_docstring(
104 'arg1', 'arg2', arg3='arg3', arg4='arg4')
105 self.assertLogLevelContains(
106 'INFO',
107 "'PageWithLogDecorator': 'Do something with docstring.'. "
108 "Arguments ('arg1', 'arg2'). "
109 "Keyword arguments: {'arg3': 'arg3', 'arg4': 'arg4'}.")
110
111 def test_logged_action_with_multiline_docstring(self):
112 self.page.do_something_with_multiline_docstring(
113 'arg1', 'arg2', arg3='arg3', arg4='arg4')
114 self.assertLogLevelContains(
115 'INFO',
116 "'PageWithLogDecorator': "
117 "'Do something with a multiline docstring.'. "
118 "Arguments ('arg1', 'arg2'). "
119 "Keyword arguments: {'arg3': 'arg3', 'arg4': 'arg4'}.")
61120
=== modified file 'u1testutils/sst/sso/pages.py'
--- u1testutils/sst/sso/pages.py 2013-04-10 19:47:20 +0000
+++ u1testutils/sst/sso/pages.py 2013-04-12 15:18:27 +0000
@@ -12,10 +12,14 @@
12# You should have received a copy of the GNU General Public License along12# You should have received a copy of the GNU General Public License along
13# with this program. If not, see <http://www.gnu.org/licenses/>.13# with this program. If not, see <http://www.gnu.org/licenses/>.
1414
15import logging
16
15import sst.actions17import sst.actions
1618
17import u1testutils.sst19import u1testutils.sst
1820
21from u1testutils.sst import log_action
22
1923
20class LogIn(u1testutils.sst.Page):24class LogIn(u1testutils.sst.Page):
21 """Log in page of the Ubuntu Single Sign On website.25 """Log in page of the Ubuntu Single Sign On website.
@@ -39,6 +43,7 @@
39 sst.actions.assert_element(tag='h1', text='Ubuntu Single Sign On')43 sst.actions.assert_element(tag='h1', text='Ubuntu Single Sign On')
40 sst.actions.assert_element(tag='h2', text='Are you new?')44 sst.actions.assert_element(tag='h2', text='Are you new?')
4145
46 @log_action(logging.info)
42 def log_in_to_site_recognized(self, user=None):47 def log_in_to_site_recognized(self, user=None):
43 """Fill the log in form and continue to the site that requested it.48 """Fill the log in form and continue to the site that requested it.
4449
@@ -51,6 +56,7 @@
51 """56 """
52 self._log_in(user)57 self._log_in(user)
5358
59 @log_action(logging.info)
54 def log_in_to_site_not_recognized(self, user=None):60 def log_in_to_site_not_recognized(self, user=None):
55 """Fill the log in form and continue to the next step.61 """Fill the log in form and continue to the next step.
5662
@@ -87,6 +93,7 @@
87 css_class='btn', name='continue')93 css_class='btn', name='continue')
88 sst.actions.click_button(continue_button)94 sst.actions.click_button(continue_button)
8995
96 @log_action(logging.info)
90 def go_to_create_new_account(self):97 def go_to_create_new_account(self):
91 """Go to the Create new account page."""98 """Go to the Create new account page."""
92 new_account_link = sst.actions.get_element(id='new-account-link')99 new_account_link = sst.actions.get_element(id='new-account-link')
@@ -109,6 +116,7 @@
109 title = 'Create account'116 title = 'Create account'
110 url_path = '/\+new_account'117 url_path = '/\+new_account'
111118
119 @log_action(logging.info)
112 def create_ubuntu_sso_account(self, user):120 def create_ubuntu_sso_account(self, user):
113 """Fill the new account form and continue to the next step.121 """Fill the new account form and continue to the next step.
114122
@@ -147,6 +155,7 @@
147 title = 'Registration mail sent'155 title = 'Registration mail sent'
148 url_path = '/\+new_account'156 url_path = '/\+new_account'
149157
158 @log_action(logging.info)
150 def confirm_email_to_site_recognized(self, confirmation_code):159 def confirm_email_to_site_recognized(self, confirmation_code):
151 """Confirm email and continue to the site that requested the log in.160 """Confirm email and continue to the site that requested the log in.
152161
@@ -157,6 +166,7 @@
157 """166 """
158 self._confirm_email(confirmation_code)167 self._confirm_email(confirmation_code)
159168
169 @log_action(logging.info)
160 def confirm_email_to_site_not_recognized(self, confirmation_code):170 def confirm_email_to_site_not_recognized(self, confirmation_code):
161 """Enter the confirmation code and continue to the next step.171 """Enter the confirmation code and continue to the next step.
162172
@@ -215,10 +225,12 @@
215 cancel_link = sst.actions.get_element(tag='a', text='cancel')225 cancel_link = sst.actions.get_element(tag='a', text='cancel')
216 sst.actions.click_link(cancel_link)226 sst.actions.click_link(cancel_link)
217227
228 @log_action(logging.info)
218 def confirm(self):229 def confirm(self):
219 self._click_continue_button()230 self._click_continue_button()
220 return YourAccount(self.user_name)231 return YourAccount(self.user_name)
221232
233 @log_action(logging.info)
222 def cancel(self):234 def cancel(self):
223 return YourAccount(self.user_name)235 return YourAccount(self.user_name)
224236
@@ -249,6 +261,7 @@
249 """261 """
250 sst.actions.assert_title_contains(self.title, regex=True)262 sst.actions.assert_title_contains(self.title, regex=True)
251263
264 @log_action(logging.info)
252 def make_all_information_available_to_website(self):265 def make_all_information_available_to_website(self):
253 """Select all the user available information.266 """Select all the user available information.
254267
@@ -265,6 +278,7 @@
265 'form[name="decideform"] > .info-items > .list > li > '278 'form[name="decideform"] > .info-items > .list > li > '
266 'input[type="checkbox"]')279 'input[type="checkbox"]')
267280
281 @log_action(logging.info)
268 def yes_sign_me_in(self):282 def yes_sign_me_in(self):
269 """Accept to sign in to the site not recognized and go back to it."""283 """Accept to sign in to the site not recognized and go back to it."""
270 sign_me_in_button = sst.actions.get_element(css_class='btn',284 sign_me_in_button = sst.actions.get_element(css_class='btn',
@@ -299,6 +313,7 @@
299313
300class _UserSubHeader(object):314class _UserSubHeader(object):
301315
316 @log_action(logging.info)
302 def log_out(self):317 def log_out(self):
303 """Log out from the web site."""318 """Log out from the web site."""
304 sst.actions.click_link('logout-link')319 sst.actions.click_link('logout-link')

Subscribers

People subscribed via source and target branches

to all changes: