2 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Unittests for stats."""
8 from __future__ import print_function
16 sys.path.insert(0, os.path.abspath('%s/../..' % os.path.dirname(__file__)))
17 from chromite.lib import cros_test_lib
18 from chromite.lib import parallel
19 from chromite.lib import parallel_unittest
20 from chromite.lib import partial_mock
21 from chromite.lib import stats
22 from chromite.lib import timeout_util
25 # pylint: disable=W0212
28 class StatsUploaderMock(partial_mock.PartialMock):
29 """Mocks out stats.StatsUploader."""
31 TARGET = 'chromite.lib.stats.StatsUploader'
32 ATTRS = ('URL', 'UPLOAD_TIMEOUT', '_Upload')
35 # Increased timeout so that we don't get errors when the machine is loaded.
38 def _Upload(self, _inst, *_args, **_kwargs):
39 """Disable actual uploading."""
43 class StatsMock(partial_mock.PartialMock):
44 """Mocks out stats.Stats."""
46 TARGET = 'chromite.lib.stats.Stats'
50 partial_mock.PartialMock.__init__(self)
51 self.init_exception = False
53 def _target__init__(self, _inst, **kwargs):
54 """Fill in good values for username and host."""
55 if self.init_exception:
56 raise Exception('abc')
58 kwargs.setdefault('username', 'monkey@google.com')
59 kwargs.setdefault('host', 'typewriter.mtv.corp.google.com')
60 return self.backup['__init__'](_inst, **kwargs)
63 class StatsModuleMock(partial_mock.PartialMock):
64 """Mock out everything needed to use this module."""
67 partial_mock.PartialMock.__init__(self)
68 self.uploader_mock = StatsUploaderMock()
69 self.stats_mock = StatsMock()
70 self.parallel_mock = parallel_unittest.ParallelMock()
73 self.StartPatcher(self.uploader_mock)
74 self.StartPatcher(self.stats_mock)
75 self.StartPatcher(self.parallel_mock)
78 class StatsCreationTest(cros_test_lib.MockLoggingTestCase):
79 """Test the stats creation functionality."""
81 def VerifyStats(self, cmd_stat):
82 self.assertNotEquals(cmd_stat.host, None)
83 self.assertNotEquals(cmd_stat.username, None)
86 """Test normal stats creation, exercising default functionality."""
87 cmd_stat = stats.Stats()
88 self.VerifyStats(cmd_stat)
90 def testSafeInitNormal(self):
91 """Test normal safe stats creation."""
92 cmd_stat = stats.Stats.SafeInit()
93 self.VerifyStats(cmd_stat)
95 def testSafeInitException(self):
96 """Safe stats creation handles exceptions properly."""
97 with cros_test_lib.LoggingCapturer() as logs:
98 cmd_stat = stats.Stats.SafeInit(monkey='foon')
99 self.assertEquals(cmd_stat, None)
100 self.AssertLogsContain(logs, 'Exception')
103 class ConditionsTest(cros_test_lib.MockTestCase):
104 """Test UploadConditionsMet."""
106 def testConditionsMet(self):
108 username='chrome-bot@chromium.org', host='build42-m2.golo.chromium.org')
109 self.assertTrue(stats.StatsUploader._UploadConditionsMet(stat))
111 def testConditionsMet2(self):
113 username='monkey@google.com', host='typewriter.mtv.corp.google.com')
114 self.assertTrue(stats.StatsUploader._UploadConditionsMet(stat))
116 def testConditionsNotMet(self):
118 username='monkey@home.com', host='typewriter.mtv.corp.google.com')
119 self.assertFalse(stats.StatsUploader._UploadConditionsMet(stat))
121 def testConditionsNotMet2(self):
123 username='monkey@google.com', host='typewriter.noname.com')
124 self.assertFalse(stats.StatsUploader._UploadConditionsMet(stat))
127 class UploadTest(cros_test_lib.MockLoggingTestCase):
128 """Test the upload functionality.
130 For the tests that validate debug log messages are printed, note that unit
131 tests are run with debug level logging.DEBUG, so logging.debug() messages in
132 the code under test will be displayed.
136 self.module_mock = StatsModuleMock()
137 self.StartPatcher(self.module_mock)
139 self.cmd_stats = stats.Stats(
140 host='test.golo.chromium.org', username='chrome-bot@chromium.org')
142 def testNormalRun(self):
143 """Going for code coverage."""
144 self.module_mock.uploader_mock.UnMockAttr('_Upload')
145 self.PatchObject(urllib2, 'urlopen', autospec=True)
146 stats.StatsUploader.Upload(self.cmd_stats)
147 with cros_test_lib.LoggingCapturer() as logs:
148 # pylint: disable=E1101
149 self.assertEquals(urllib2.urlopen.call_count, 1)
150 # Make sure no error messages are output in the normal case.
151 self.AssertLogsContain(logs, stats.StatsUploader.ENVIRONMENT_ERROR,
153 timeout_regex = stats.StatsUploader.TIMEOUT_ERROR % '\d+'
154 self.AssertLogsMatch(logs, timeout_regex, inverted=True)
156 def CheckSuppressException(self, e, msg):
157 """Verifies we don't propagate a given exception during upload."""
158 with cros_test_lib.LoggingCapturer() as logs:
159 stats.StatsUploader._Upload.side_effect = e
160 # Verify the exception is suppressed when error_ok=True
161 stats.StatsUploader.Upload(self.cmd_stats)
162 # Note: the default log level for unit tests is logging.DEBUG
163 self.AssertLogsContain(logs, msg)
165 def testUploadTimeoutIgnore(self):
166 """We don't propagate timeouts during upload."""
167 self.CheckSuppressException(
168 timeout_util.TimeoutError(),
169 stats.StatsUploader.TIMEOUT_ERROR
170 % (stats.StatsUploader.UPLOAD_TIMEOUT,))
172 def testEnvironmentErrorIgnore(self):
173 """We don't propagate any environment errors during upload."""
174 url = 'http://somedomainhere.com/foo/bar/uploader'
175 env_msg = stats.StatsUploader.ENVIRONMENT_ERROR
176 url_msg = stats.StatsUploader.HTTPURL_ERROR % url
177 self.CheckSuppressException(EnvironmentError(), env_msg)
178 self.CheckSuppressException(urllib2.HTTPError(url, None, None, None, None),
180 self.CheckSuppressException(urllib2.URLError(""), env_msg)
182 def testKeyboardInterruptError(self):
183 """We propagate KeyboardInterrupts."""
184 stats.StatsUploader._Upload.side_effect = KeyboardInterrupt()
185 # Verify the exception is suppressed when error_ok=True
186 self.assertRaises(KeyboardInterrupt, stats.StatsUploader.Upload,
189 def testUploadTimeout(self):
190 """We timeout when the upload takes too long."""
191 def Sleep(*_args, **_kwargs):
192 time.sleep(stats.StatsUploader.UPLOAD_TIMEOUT)
194 stats.StatsUploader._Upload.side_effect = Sleep
195 with cros_test_lib.LoggingCapturer() as logs:
196 stats.StatsUploader.Upload(self.cmd_stats, timeout=1)
197 self.AssertLogsContain(logs, stats.StatsUploader.TIMEOUT_ERROR % ('1',))
200 class UploadContextTest(cros_test_lib.MockLoggingTestCase):
201 """Test the suppression behavior of the upload context."""
204 self.StartPatcher(StatsModuleMock())
206 def testNoErrors(self):
207 """Test that we don't print anything when there are no errors."""
208 with cros_test_lib.LoggingCapturer() as logs:
209 with stats.UploadContext() as queue:
210 queue.put([stats.Stats()])
211 self.AssertLogsContain(logs, stats.UNCAUGHT_UPLOAD_ERROR, inverted=True)
212 self.assertEquals(stats.StatsUploader._Upload.call_count, 1)
214 def testErrorSupression(self):
215 """"Test exception supression."""
216 for e in [parallel.BackgroundFailure]:
217 with cros_test_lib.LoggingCapturer() as logs:
218 with stats.UploadContext():
220 self.AssertLogsContain(logs, stats.UNCAUGHT_UPLOAD_ERROR)
222 def testErrorPropagation(self):
223 """Test we propagate some exceptions."""
225 with stats.UploadContext():
228 for e in [KeyboardInterrupt, RuntimeError, Exception, BaseException,
230 self.assertRaises(e, RaiseContext, e)
233 if __name__ == '__main__':
234 cros_test_lib.main(level=logging.DEBUG)