~cloud-init-dev/cloud-init/trunk

« back to all changes in this revision

Viewing changes to tests/unittests/test_reporting.py

  • Committer: Scott Moser
  • Date: 2016-08-10 15:06:15 UTC
  • Revision ID: smoser@ubuntu.com-20160810150615-ma2fv107w3suy1ma
README: Mention move of revision control to git.

cloud-init development has moved its revision control to git.
It is available at 
  https://code.launchpad.net/cloud-init

Clone with 
  git clone https://git.launchpad.net/cloud-init
or
  git clone git+ssh://git.launchpad.net/cloud-init

For more information see
  https://git.launchpad.net/cloud-init/tree/HACKING.rst

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2015 Canonical Ltd.
2
 
# This file is part of cloud-init.  See LICENCE file for license information.
3
 
#
4
 
# vi: ts=4 expandtab
5
 
 
6
 
from cloudinit import reporting
7
 
from cloudinit.reporting import events
8
 
from cloudinit.reporting import handlers
9
 
 
10
 
import mock
11
 
 
12
 
from .helpers import TestCase
13
 
 
14
 
 
15
 
def _fake_registry():
16
 
    return mock.Mock(registered_items={'a': mock.MagicMock(),
17
 
                                       'b': mock.MagicMock()})
18
 
 
19
 
 
20
 
class TestReportStartEvent(TestCase):
21
 
 
22
 
    @mock.patch('cloudinit.reporting.events.instantiated_handler_registry',
23
 
                new_callable=_fake_registry)
24
 
    def test_report_start_event_passes_something_with_as_string_to_handlers(
25
 
            self, instantiated_handler_registry):
26
 
        event_name, event_description = 'my_test_event', 'my description'
27
 
        events.report_start_event(event_name, event_description)
28
 
        expected_string_representation = ': '.join(
29
 
            ['start', event_name, event_description])
30
 
        for _, handler in (
31
 
                instantiated_handler_registry.registered_items.items()):
32
 
            self.assertEqual(1, handler.publish_event.call_count)
33
 
            event = handler.publish_event.call_args[0][0]
34
 
            self.assertEqual(expected_string_representation, event.as_string())
35
 
 
36
 
 
37
 
class TestReportFinishEvent(TestCase):
38
 
 
39
 
    def _report_finish_event(self, result=events.status.SUCCESS):
40
 
        event_name, event_description = 'my_test_event', 'my description'
41
 
        events.report_finish_event(
42
 
            event_name, event_description, result=result)
43
 
        return event_name, event_description
44
 
 
45
 
    def assertHandlersPassedObjectWithAsString(
46
 
            self, handlers, expected_as_string):
47
 
        for _, handler in handlers.items():
48
 
            self.assertEqual(1, handler.publish_event.call_count)
49
 
            event = handler.publish_event.call_args[0][0]
50
 
            self.assertEqual(expected_as_string, event.as_string())
51
 
 
52
 
    @mock.patch('cloudinit.reporting.events.instantiated_handler_registry',
53
 
                new_callable=_fake_registry)
54
 
    def test_report_finish_event_passes_something_with_as_string_to_handlers(
55
 
            self, instantiated_handler_registry):
56
 
        event_name, event_description = self._report_finish_event()
57
 
        expected_string_representation = ': '.join(
58
 
            ['finish', event_name, events.status.SUCCESS,
59
 
             event_description])
60
 
        self.assertHandlersPassedObjectWithAsString(
61
 
            instantiated_handler_registry.registered_items,
62
 
            expected_string_representation)
63
 
 
64
 
    @mock.patch('cloudinit.reporting.events.instantiated_handler_registry',
65
 
                new_callable=_fake_registry)
66
 
    def test_reporting_successful_finish_has_sensible_string_repr(
67
 
            self, instantiated_handler_registry):
68
 
        event_name, event_description = self._report_finish_event(
69
 
            result=events.status.SUCCESS)
70
 
        expected_string_representation = ': '.join(
71
 
            ['finish', event_name, events.status.SUCCESS,
72
 
             event_description])
73
 
        self.assertHandlersPassedObjectWithAsString(
74
 
            instantiated_handler_registry.registered_items,
75
 
            expected_string_representation)
76
 
 
77
 
    @mock.patch('cloudinit.reporting.events.instantiated_handler_registry',
78
 
                new_callable=_fake_registry)
79
 
    def test_reporting_unsuccessful_finish_has_sensible_string_repr(
80
 
            self, instantiated_handler_registry):
81
 
        event_name, event_description = self._report_finish_event(
82
 
            result=events.status.FAIL)
83
 
        expected_string_representation = ': '.join(
84
 
            ['finish', event_name, events.status.FAIL, event_description])
85
 
        self.assertHandlersPassedObjectWithAsString(
86
 
            instantiated_handler_registry.registered_items,
87
 
            expected_string_representation)
88
 
 
89
 
    def test_invalid_result_raises_attribute_error(self):
90
 
        self.assertRaises(ValueError, self._report_finish_event, ("BOGUS",))
91
 
 
92
 
 
93
 
class TestReportingEvent(TestCase):
94
 
 
95
 
    def test_as_string(self):
96
 
        event_type, name, description = 'test_type', 'test_name', 'test_desc'
97
 
        event = events.ReportingEvent(event_type, name, description)
98
 
        expected_string_representation = ': '.join(
99
 
            [event_type, name, description])
100
 
        self.assertEqual(expected_string_representation, event.as_string())
101
 
 
102
 
    def test_as_dict(self):
103
 
        event_type, name, desc = 'test_type', 'test_name', 'test_desc'
104
 
        event = events.ReportingEvent(event_type, name, desc)
105
 
        expected = {'event_type': event_type, 'name': name,
106
 
                    'description': desc, 'origin': 'cloudinit'}
107
 
 
108
 
        # allow for timestamp to differ, but must be present
109
 
        as_dict = event.as_dict()
110
 
        self.assertIn('timestamp', as_dict)
111
 
        del as_dict['timestamp']
112
 
 
113
 
        self.assertEqual(expected, as_dict)
114
 
 
115
 
 
116
 
class TestFinishReportingEvent(TestCase):
117
 
    def test_as_has_result(self):
118
 
        result = events.status.SUCCESS
119
 
        name, desc = 'test_name', 'test_desc'
120
 
        event = events.FinishReportingEvent(name, desc, result)
121
 
        ret = event.as_dict()
122
 
        self.assertTrue('result' in ret)
123
 
        self.assertEqual(ret['result'], result)
124
 
 
125
 
 
126
 
class TestBaseReportingHandler(TestCase):
127
 
 
128
 
    def test_base_reporting_handler_is_abstract(self):
129
 
        regexp = r".*abstract.*publish_event.*"
130
 
        self.assertRaisesRegexp(TypeError, regexp, handlers.ReportingHandler)
131
 
 
132
 
 
133
 
class TestLogHandler(TestCase):
134
 
 
135
 
    @mock.patch.object(reporting.handlers.logging, 'getLogger')
136
 
    def test_appropriate_logger_used(self, getLogger):
137
 
        event_type, event_name = 'test_type', 'test_name'
138
 
        event = events.ReportingEvent(event_type, event_name, 'description')
139
 
        reporting.handlers.LogHandler().publish_event(event)
140
 
        self.assertEqual(
141
 
            [mock.call(
142
 
                'cloudinit.reporting.{0}.{1}'.format(event_type, event_name))],
143
 
            getLogger.call_args_list)
144
 
 
145
 
    @mock.patch.object(reporting.handlers.logging, 'getLogger')
146
 
    def test_single_log_message_at_info_published(self, getLogger):
147
 
        event = events.ReportingEvent('type', 'name', 'description')
148
 
        reporting.handlers.LogHandler().publish_event(event)
149
 
        self.assertEqual(1, getLogger.return_value.log.call_count)
150
 
 
151
 
    @mock.patch.object(reporting.handlers.logging, 'getLogger')
152
 
    def test_log_message_uses_event_as_string(self, getLogger):
153
 
        event = events.ReportingEvent('type', 'name', 'description')
154
 
        reporting.handlers.LogHandler(level="INFO").publish_event(event)
155
 
        self.assertIn(event.as_string(),
156
 
                      getLogger.return_value.log.call_args[0][1])
157
 
 
158
 
 
159
 
class TestDefaultRegisteredHandler(TestCase):
160
 
 
161
 
    def test_log_handler_registered_by_default(self):
162
 
        registered_items = (
163
 
            reporting.instantiated_handler_registry.registered_items)
164
 
        for _, item in registered_items.items():
165
 
            if isinstance(item, reporting.handlers.LogHandler):
166
 
                break
167
 
        else:
168
 
            self.fail('No reporting LogHandler registered by default.')
169
 
 
170
 
 
171
 
class TestReportingConfiguration(TestCase):
172
 
 
173
 
    @mock.patch.object(reporting, 'instantiated_handler_registry')
174
 
    def test_empty_configuration_doesnt_add_handlers(
175
 
            self, instantiated_handler_registry):
176
 
        reporting.update_configuration({})
177
 
        self.assertEqual(
178
 
            0, instantiated_handler_registry.register_item.call_count)
179
 
 
180
 
    @mock.patch.object(
181
 
        reporting, 'instantiated_handler_registry', reporting.DictRegistry())
182
 
    @mock.patch.object(reporting, 'available_handlers')
183
 
    def test_looks_up_handler_by_type_and_adds_it(self, available_handlers):
184
 
        handler_type_name = 'test_handler'
185
 
        handler_cls = mock.Mock()
186
 
        available_handlers.registered_items = {handler_type_name: handler_cls}
187
 
        handler_name = 'my_test_handler'
188
 
        reporting.update_configuration(
189
 
            {handler_name: {'type': handler_type_name}})
190
 
        self.assertEqual(
191
 
            {handler_name: handler_cls.return_value},
192
 
            reporting.instantiated_handler_registry.registered_items)
193
 
 
194
 
    @mock.patch.object(
195
 
        reporting, 'instantiated_handler_registry', reporting.DictRegistry())
196
 
    @mock.patch.object(reporting, 'available_handlers')
197
 
    def test_uses_non_type_parts_of_config_dict_as_kwargs(
198
 
            self, available_handlers):
199
 
        handler_type_name = 'test_handler'
200
 
        handler_cls = mock.Mock()
201
 
        available_handlers.registered_items = {handler_type_name: handler_cls}
202
 
        extra_kwargs = {'foo': 'bar', 'bar': 'baz'}
203
 
        handler_config = extra_kwargs.copy()
204
 
        handler_config.update({'type': handler_type_name})
205
 
        handler_name = 'my_test_handler'
206
 
        reporting.update_configuration({handler_name: handler_config})
207
 
        self.assertEqual(
208
 
            handler_cls.return_value,
209
 
            reporting.instantiated_handler_registry.registered_items[
210
 
                handler_name])
211
 
        self.assertEqual([mock.call(**extra_kwargs)],
212
 
                         handler_cls.call_args_list)
213
 
 
214
 
    @mock.patch.object(
215
 
        reporting, 'instantiated_handler_registry', reporting.DictRegistry())
216
 
    @mock.patch.object(reporting, 'available_handlers')
217
 
    def test_handler_config_not_modified(self, available_handlers):
218
 
        handler_type_name = 'test_handler'
219
 
        handler_cls = mock.Mock()
220
 
        available_handlers.registered_items = {handler_type_name: handler_cls}
221
 
        handler_config = {'type': handler_type_name, 'foo': 'bar'}
222
 
        expected_handler_config = handler_config.copy()
223
 
        reporting.update_configuration({'my_test_handler': handler_config})
224
 
        self.assertEqual(expected_handler_config, handler_config)
225
 
 
226
 
    @mock.patch.object(
227
 
        reporting, 'instantiated_handler_registry', reporting.DictRegistry())
228
 
    @mock.patch.object(reporting, 'available_handlers')
229
 
    def test_handlers_removed_if_falseish_specified(self, available_handlers):
230
 
        handler_type_name = 'test_handler'
231
 
        handler_cls = mock.Mock()
232
 
        available_handlers.registered_items = {handler_type_name: handler_cls}
233
 
        handler_name = 'my_test_handler'
234
 
        reporting.update_configuration(
235
 
            {handler_name: {'type': handler_type_name}})
236
 
        self.assertEqual(
237
 
            1, len(reporting.instantiated_handler_registry.registered_items))
238
 
        reporting.update_configuration({handler_name: None})
239
 
        self.assertEqual(
240
 
            0, len(reporting.instantiated_handler_registry.registered_items))
241
 
 
242
 
 
243
 
class TestReportingEventStack(TestCase):
244
 
    @mock.patch('cloudinit.reporting.events.report_finish_event')
245
 
    @mock.patch('cloudinit.reporting.events.report_start_event')
246
 
    def test_start_and_finish_success(self, report_start, report_finish):
247
 
        with events.ReportEventStack(name="myname", description="mydesc"):
248
 
            pass
249
 
        self.assertEqual(
250
 
            [mock.call('myname', 'mydesc')], report_start.call_args_list)
251
 
        self.assertEqual(
252
 
            [mock.call('myname', 'mydesc', events.status.SUCCESS,
253
 
                       post_files=[])],
254
 
            report_finish.call_args_list)
255
 
 
256
 
    @mock.patch('cloudinit.reporting.events.report_finish_event')
257
 
    @mock.patch('cloudinit.reporting.events.report_start_event')
258
 
    def test_finish_exception_defaults_fail(self, report_start, report_finish):
259
 
        name = "myname"
260
 
        desc = "mydesc"
261
 
        try:
262
 
            with events.ReportEventStack(name, description=desc):
263
 
                raise ValueError("This didnt work")
264
 
        except ValueError:
265
 
            pass
266
 
        self.assertEqual([mock.call(name, desc)], report_start.call_args_list)
267
 
        self.assertEqual(
268
 
            [mock.call(name, desc, events.status.FAIL, post_files=[])],
269
 
            report_finish.call_args_list)
270
 
 
271
 
    @mock.patch('cloudinit.reporting.events.report_finish_event')
272
 
    @mock.patch('cloudinit.reporting.events.report_start_event')
273
 
    def test_result_on_exception_used(self, report_start, report_finish):
274
 
        name = "myname"
275
 
        desc = "mydesc"
276
 
        try:
277
 
            with events.ReportEventStack(
278
 
                    name, desc, result_on_exception=events.status.WARN):
279
 
                raise ValueError("This didnt work")
280
 
        except ValueError:
281
 
            pass
282
 
        self.assertEqual([mock.call(name, desc)], report_start.call_args_list)
283
 
        self.assertEqual(
284
 
            [mock.call(name, desc, events.status.WARN, post_files=[])],
285
 
            report_finish.call_args_list)
286
 
 
287
 
    @mock.patch('cloudinit.reporting.events.report_start_event')
288
 
    def test_child_fullname_respects_parent(self, report_start):
289
 
        parent_name = "topname"
290
 
        c1_name = "c1name"
291
 
        c2_name = "c2name"
292
 
        c2_expected_fullname = '/'.join([parent_name, c1_name, c2_name])
293
 
        c1_expected_fullname = '/'.join([parent_name, c1_name])
294
 
 
295
 
        parent = events.ReportEventStack(parent_name, "topdesc")
296
 
        c1 = events.ReportEventStack(c1_name, "c1desc", parent=parent)
297
 
        c2 = events.ReportEventStack(c2_name, "c2desc", parent=c1)
298
 
        with c1:
299
 
            report_start.assert_called_with(c1_expected_fullname, "c1desc")
300
 
            with c2:
301
 
                report_start.assert_called_with(c2_expected_fullname, "c2desc")
302
 
 
303
 
    @mock.patch('cloudinit.reporting.events.report_finish_event')
304
 
    @mock.patch('cloudinit.reporting.events.report_start_event')
305
 
    def test_child_result_bubbles_up(self, report_start, report_finish):
306
 
        parent = events.ReportEventStack("topname", "topdesc")
307
 
        child = events.ReportEventStack("c_name", "c_desc", parent=parent)
308
 
        with parent:
309
 
            with child:
310
 
                child.result = events.status.WARN
311
 
 
312
 
        report_finish.assert_called_with(
313
 
            "topname", "topdesc", events.status.WARN, post_files=[])
314
 
 
315
 
    @mock.patch('cloudinit.reporting.events.report_finish_event')
316
 
    def test_message_used_in_finish(self, report_finish):
317
 
        with events.ReportEventStack("myname", "mydesc",
318
 
                                     message="mymessage"):
319
 
            pass
320
 
        self.assertEqual(
321
 
            [mock.call("myname", "mymessage", events.status.SUCCESS,
322
 
                       post_files=[])],
323
 
            report_finish.call_args_list)
324
 
 
325
 
    @mock.patch('cloudinit.reporting.events.report_finish_event')
326
 
    def test_message_updatable(self, report_finish):
327
 
        with events.ReportEventStack("myname", "mydesc") as c:
328
 
            c.message = "all good"
329
 
        self.assertEqual(
330
 
            [mock.call("myname", "all good", events.status.SUCCESS,
331
 
                       post_files=[])],
332
 
            report_finish.call_args_list)
333
 
 
334
 
    @mock.patch('cloudinit.reporting.events.report_start_event')
335
 
    @mock.patch('cloudinit.reporting.events.report_finish_event')
336
 
    def test_reporting_disabled_does_not_report_events(
337
 
            self, report_start, report_finish):
338
 
        with events.ReportEventStack("a", "b", reporting_enabled=False):
339
 
            pass
340
 
        self.assertEqual(report_start.call_count, 0)
341
 
        self.assertEqual(report_finish.call_count, 0)
342
 
 
343
 
    @mock.patch('cloudinit.reporting.events.report_start_event')
344
 
    @mock.patch('cloudinit.reporting.events.report_finish_event')
345
 
    def test_reporting_child_default_to_parent(
346
 
            self, report_start, report_finish):
347
 
        parent = events.ReportEventStack(
348
 
            "pname", "pdesc", reporting_enabled=False)
349
 
        child = events.ReportEventStack("cname", "cdesc", parent=parent)
350
 
        with parent:
351
 
            with child:
352
 
                pass
353
 
            pass
354
 
        self.assertEqual(report_start.call_count, 0)
355
 
        self.assertEqual(report_finish.call_count, 0)
356
 
 
357
 
    def test_reporting_event_has_sane_repr(self):
358
 
        myrep = events.ReportEventStack("fooname", "foodesc",
359
 
                                        reporting_enabled=True).__repr__()
360
 
        self.assertIn("fooname", myrep)
361
 
        self.assertIn("foodesc", myrep)
362
 
        self.assertIn("True", myrep)
363
 
 
364
 
    def test_set_invalid_result_raises_value_error(self):
365
 
        f = events.ReportEventStack("myname", "mydesc")
366
 
        self.assertRaises(ValueError, setattr, f, "result", "BOGUS")
367
 
 
368
 
 
369
 
class TestStatusAccess(TestCase):
370
 
    def test_invalid_status_access_raises_value_error(self):
371
 
        self.assertRaises(AttributeError, getattr, events.status, "BOGUS")