1#!/usr/bin/env python3
2#
3#   Copyright 2018 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17from mock import Mock
18from mock import patch
19import unittest
20from unittest import TestCase
21from acts.metrics.logger import LoggerProxy
22from acts.metrics.logger import MetricLogger
23
24COMPILE_IMPORT_PROTO = 'acts.metrics.logger.compile_import_proto'
25CREATE_FROM_INSTANCE = (
26    'acts.metrics.logger.subscription_bundle.create_from_instance')
27LOGGING_ERROR = 'logging.error'
28LOGGING_DEBUG = 'logging.debug'
29GET_CONTEXT_FOR_EVENT = 'acts.metrics.logger.get_context_for_event'
30GET_FILE = 'acts.metrics.logger.inspect.getfile'
31MKDTEMP = 'acts.metrics.logger.tempfile.mkdtemp'
32PROTO_METRIC_PUBLISHER = 'acts.metrics.logger.ProtoMetricPublisher'
33TEST_CASE_LOGGER_PROXY = 'acts.metrics.logger.TestCaseLoggerProxy'
34TEST_CLASS_LOGGER_PROXY = 'acts.metrics.logger.TestClassLoggerProxy'
35
36
37class MetricLoggerTest(TestCase):
38    """Unit tests for the MetricLogger class."""
39
40    @patch(TEST_CASE_LOGGER_PROXY)
41    def test_for_test_case_returns_test_case_proxy(self, proxy_cls):
42        args = (Mock(), )
43        kwargs = {'mock' : Mock()}
44        logger = MetricLogger.for_test_case(*args, **kwargs)
45
46        proxy_cls.assert_called_once_with(MetricLogger, args, kwargs)
47
48    @patch(TEST_CLASS_LOGGER_PROXY)
49    def test_for_test_class_returns_test_class_proxy(self, proxy_cls):
50        args = (Mock(),)
51        kwargs = {'mock': Mock()}
52        logger = MetricLogger.for_test_class(*args, **kwargs)
53
54        proxy_cls.assert_called_once_with(MetricLogger, args, kwargs)
55
56    @patch(TEST_CASE_LOGGER_PROXY)
57    def test_for_test_case_works_on_subclases(self, proxy_cls):
58        class TestLogger(MetricLogger):
59            pass
60        args = (Mock(),)
61        kwargs = {'mock': Mock()}
62        logger = TestLogger.for_test_case(*args, **kwargs)
63
64        proxy_cls.assert_called_once_with(TestLogger, args, kwargs)
65
66    @patch(TEST_CLASS_LOGGER_PROXY)
67    def test_for_test_class_works_on_subclases(self, proxy_cls):
68        class TestLogger(MetricLogger):
69            pass
70        args = (Mock(),)
71        kwargs = {'mock': Mock()}
72        logger = TestLogger.for_test_class(*args, **kwargs)
73
74        proxy_cls.assert_called_once_with(TestLogger, args, kwargs)
75
76    @patch(COMPILE_IMPORT_PROTO)
77    @patch(GET_FILE)
78    def test_compile_proto_relative_path(self, getfile, compile_import_proto):
79        getfile.return_value = '/path/to/class/file.py'
80        proto_path = 'dir/my_proto.proto'
81        compiler_out = Mock()
82        MetricLogger._compile_proto(proto_path, compiler_out=compiler_out)
83
84        full_proto_path = '/path/to/class/dir/my_proto.proto'
85        compile_import_proto.assert_called_once_with(
86            compiler_out, full_proto_path)
87
88    @patch(COMPILE_IMPORT_PROTO)
89    @patch(GET_FILE)
90    def test_compile_proto_absolute_path(self, getfile, compile_import_proto):
91        proto_path = '/abs/path/to/my_proto.proto'
92        compiler_out = Mock()
93        MetricLogger._compile_proto(proto_path, compiler_out=compiler_out)
94
95        compile_import_proto.assert_called_once_with(compiler_out, proto_path)
96        getfile.assert_not_called()
97
98    @patch(COMPILE_IMPORT_PROTO)
99    @patch(GET_FILE)
100    @patch(MKDTEMP)
101    def test_compile_proto_default_compiler_out(self,
102                                                mkdtemp,
103                                                getfile,
104                                                compile_import_proto):
105        compiler_out = Mock()
106        mkdtemp.return_value = compiler_out
107        proto_path = '/abs/path/to/my_proto.proto'
108        MetricLogger._compile_proto(proto_path)
109
110        compile_import_proto.assert_called_once_with(compiler_out, proto_path)
111
112
113    def test_init_empty(self):
114        logger = MetricLogger()
115
116        self.assertIsNone(logger.context)
117        self.assertIsNone(logger.publisher)
118
119    def test_init_with_context_and_publisher(self):
120        context = Mock()
121        publisher = Mock()
122
123        logger = MetricLogger(context=context, publisher=publisher)
124
125        self.assertEqual(logger.context, context)
126        self.assertEqual(logger.publisher, publisher)
127
128    @patch(PROTO_METRIC_PUBLISHER)
129    @patch(GET_CONTEXT_FOR_EVENT)
130    def test_init_with_event(self, get_context, publisher_cls):
131        context = Mock()
132        publisher = Mock()
133        get_context.return_value = context
134        publisher_cls.return_value = publisher
135        event = Mock()
136
137        logger = MetricLogger(event=event)
138
139        get_context.assert_called_once_with(event)
140        publisher_cls.assert_called_once_with(context)
141        self.assertEqual(logger.context, context)
142        self.assertEqual(logger.publisher, publisher)
143
144    def test_start_has_default_impl(self):
145        logger = MetricLogger()
146        logger.start(Mock())
147
148    def test_end_has_default_impl(self):
149        logger = MetricLogger()
150        logger.end(Mock())
151
152    def test_compile_proto(self):
153        logger = MetricLogger()
154
155
156class LoggerProxyTest(TestCase):
157
158    @patch(CREATE_FROM_INSTANCE)
159    def test_init(self, create_from_instance):
160        logger_cls = Mock()
161        logger_args = Mock()
162        logger_kwargs = Mock()
163        bundle = Mock()
164        create_from_instance.return_value = bundle
165        proxy = LoggerProxy(logger_cls,
166                            logger_args,
167                            logger_kwargs)
168
169        self.assertEqual(proxy._logger_cls, logger_cls)
170        self.assertEqual(proxy._logger_args, logger_args)
171        self.assertEqual(proxy._logger_kwargs, logger_kwargs)
172        self.assertIsNone(proxy._logger)
173        create_from_instance.assert_called_once_with(proxy)
174        bundle.register.assert_called_once_with()
175
176    @patch(CREATE_FROM_INSTANCE)
177    def test_setup_proxy(self, create_from_instance):
178        logger_cls = Mock()
179        logger_args = (Mock(), )
180        logger_kwargs = {'mock': Mock()}
181        bundle = Mock()
182        event = Mock()
183        create_from_instance.return_value = bundle
184        logger = Mock()
185        logger_cls.return_value = logger
186
187        proxy = LoggerProxy(logger_cls,
188                            logger_args,
189                            logger_kwargs)
190        proxy._setup_proxy(event)
191
192        logger_cls.assert_called_once_with(event=event,
193                                           *logger_args,
194                                           **logger_kwargs)
195        logger.start.assert_called_once_with(event)
196
197    @patch(CREATE_FROM_INSTANCE)
198    def test_teardown_proxy(self, create_from_instance):
199        logger_cls = Mock()
200        logger_args = (Mock(),)
201        logger_kwargs = {'mock': Mock()}
202        bundle = Mock()
203        event = Mock()
204        create_from_instance.return_value = bundle
205        logger = Mock()
206        logger_cls.return_value = logger
207
208        proxy = LoggerProxy(logger_cls,
209                            logger_args,
210                            logger_kwargs)
211        proxy._setup_proxy(event)
212        proxy._teardown_proxy(event)
213
214        logger.end.assert_called_once_with(event)
215        self.assertIsNone(proxy._logger)
216
217    @patch(LOGGING_DEBUG)
218    @patch(LOGGING_ERROR)
219    @patch(CREATE_FROM_INSTANCE)
220    def test_teardown_proxy_logs_upon_exception(self, create_from_instance,
221                                                logging_error, logging_debug):
222        logger_cls = Mock()
223        logger_args = (Mock(),)
224        logger_kwargs = {'mock': Mock()}
225        bundle = Mock()
226        event = Mock()
227        create_from_instance.return_value = bundle
228        logger = Mock()
229        logger.end.side_effect = ValueError('test')
230        logger_cls.return_value = logger
231
232        proxy = LoggerProxy(logger_cls,
233                            logger_args,
234                            logger_kwargs)
235        proxy._setup_proxy(event)
236        proxy._teardown_proxy(event)
237
238        self.assertTrue(logging_error.called)
239        self.assertTrue(logging_debug.called)
240        self.assertIsNone(proxy._logger)
241
242    @patch(CREATE_FROM_INSTANCE)
243    def test_getattr_forwards_to_logger(self, create_from_instance):
244        logger_cls = Mock()
245        logger_args = (Mock(),)
246        logger_kwargs = {'mock': Mock()}
247        bundle = Mock()
248        event = Mock()
249        create_from_instance.return_value = bundle
250        logger = Mock()
251        logger_cls.return_value = logger
252
253        proxy = LoggerProxy(logger_cls,
254                            logger_args,
255                            logger_kwargs)
256        proxy._setup_proxy(event)
257
258        self.assertEqual(proxy.some_attr, logger.some_attr)
259
260    @patch(CREATE_FROM_INSTANCE)
261    def test_getattr_with_no_logger_raises(self, create_from_instance):
262        bundle = Mock()
263        create_from_instance.return_value = bundle
264
265        proxy = LoggerProxy(Mock(), Mock(), Mock())
266
267        self.assertRaises(ValueError, lambda: proxy.some_attr)
268
269    @patch(CREATE_FROM_INSTANCE)
270    def test_setattr_forwards_to_logger(self, create_from_instance):
271        logger_cls = Mock()
272        logger_args = (Mock(),)
273        logger_kwargs = {'mock': Mock()}
274        bundle = Mock()
275        event = Mock()
276        create_from_instance.return_value = bundle
277        logger = Mock()
278        logger_cls.return_value = logger
279        value = Mock()
280
281        proxy = LoggerProxy(logger_cls,
282                            logger_args,
283                            logger_kwargs)
284        proxy._setup_proxy(event)
285        proxy.some_attr = value
286
287        self.assertEqual(logger.some_attr, value)
288
289    @patch(CREATE_FROM_INSTANCE)
290    def test_setattr_with_no_logger_raises(self, create_from_instance):
291        bundle = Mock()
292        create_from_instance.return_value = bundle
293        value = Mock()
294
295        proxy = LoggerProxy(Mock(), Mock(), Mock())
296
297        def try_set():
298            proxy.some_attr = value
299        self.assertRaises(ValueError, try_set)
300
301
302if __name__ == '__main__':
303    unittest.main()
304