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."""
14 sys.path.insert(0, os.path.abspath('%s/../..' % os.path.dirname(__file__)))
15 from chromite.lib import cros_test_lib
16 from chromite.lib import parallel
17 from chromite.lib import parallel_unittest
18 from chromite.lib import partial_mock
19 from chromite.lib import stats
20 from chromite.lib import timeout_util
23 # pylint: disable=W0212
26 class StatsUploaderMock(partial_mock.PartialMock):
27 """Mocks out stats.StatsUploader."""
29 TARGET = 'chromite.lib.stats.StatsUploader'
30 ATTRS = ('URL', 'UPLOAD_TIMEOUT', '_Upload')
33 # Increased timeout so that we don't get errors when the machine is loaded.
36 def _Upload(self, _inst, *_args, **_kwargs):
37 """Disable actual uploading."""
41 class StatsMock(partial_mock.PartialMock):
42 """Mocks out stats.Stats."""
44 TARGET = 'chromite.lib.stats.Stats'
48 partial_mock.PartialMock.__init__(self)
49 self.init_exception = False
51 def _target__init__(self, _inst, **kwargs):
52 """Fill in good values for username and host."""
53 if self.init_exception:
54 raise Exception('abc')
56 kwargs.setdefault('username', 'monkey@google.com')
57 kwargs.setdefault('host', 'typewriter.mtv.corp.google.com')
58 return self.backup['__init__'](_inst, **kwargs)
61 class StatsModuleMock(partial_mock.PartialMock):
62 """Mock out everything needed to use this module."""
65 partial_mock.PartialMock.__init__(self)
66 self.uploader_mock = StatsUploaderMock()
67 self.stats_mock = StatsMock()
68 self.parallel_mock = parallel_unittest.ParallelMock()
71 self.StartPatcher(self.uploader_mock)
72 self.StartPatcher(self.stats_mock)
73 self.StartPatcher(self.parallel_mock)
76 class StatsCreationTest(cros_test_lib.MockLoggingTestCase):
77 """Test the stats creation functionality."""
79 def VerifyStats(self, cmd_stat):
80 self.assertNotEquals(cmd_stat.host, None)
81 self.assertNotEquals(cmd_stat.username, None)
84 """Test normal stats creation, exercising default functionality."""
85 cmd_stat = stats.Stats()
86 self.VerifyStats(cmd_stat)
88 def testSafeInitNormal(self):
89 """Test normal safe stats creation."""
90 cmd_stat = stats.Stats.SafeInit()
91 self.VerifyStats(cmd_stat)
93 def testSafeInitException(self):
94 """Safe stats creation handles exceptions properly."""
95 with cros_test_lib.LoggingCapturer() as logs:
96 cmd_stat = stats.Stats.SafeInit(monkey='foon')
97 self.assertEquals(cmd_stat, None)
98 self.AssertLogsContain(logs, 'Exception')
101 class ConditionsTest(cros_test_lib.MockTestCase):
102 """Test UploadConditionsMet."""
104 def testConditionsMet(self):
106 username='chrome-bot@chromium.org', host='build42-m2.golo.chromium.org')
107 self.assertTrue(stats.StatsUploader._UploadConditionsMet(stat))
109 def testConditionsMet2(self):
111 username='monkey@google.com', host='typewriter.mtv.corp.google.com')
112 self.assertTrue(stats.StatsUploader._UploadConditionsMet(stat))
114 def testConditionsNotMet(self):
116 username='monkey@home.com', host='typewriter.mtv.corp.google.com')
117 self.assertFalse(stats.StatsUploader._UploadConditionsMet(stat))
119 def testConditionsNotMet2(self):
121 username='monkey@google.com', host='typewriter.noname.com')
122 self.assertFalse(stats.StatsUploader._UploadConditionsMet(stat))
125 class UploadTest(cros_test_lib.MockLoggingTestCase):
126 """Test the upload functionality.
128 For the tests that validate debug log messages are printed, note that unit
129 tests are run with debug level logging.DEBUG, so logging.debug() messages in
130 the code under test will be displayed.
134 self.module_mock = StatsModuleMock()
135 self.StartPatcher(self.module_mock)
137 self.cmd_stats = stats.Stats(
138 host='test.golo.chromium.org', username='chrome-bot@chromium.org')
140 def testNormalRun(self):
141 """Going for code coverage."""
142 self.module_mock.uploader_mock.UnMockAttr('_Upload')
143 self.PatchObject(urllib2, 'urlopen', autospec=True)
144 stats.StatsUploader.Upload(self.cmd_stats)
145 with cros_test_lib.LoggingCapturer() as logs:
146 # pylint: disable=E1101
147 self.assertEquals(urllib2.urlopen.call_count, 1)
148 # Make sure no error messages are output in the normal case.
149 self.AssertLogsContain(logs, stats.StatsUploader.ENVIRONMENT_ERROR,
151 timeout_regex = stats.StatsUploader.TIMEOUT_ERROR % '\d+'
152 self.AssertLogsMatch(logs, timeout_regex, inverted=True)
154 def CheckSuppressException(self, e, msg):
155 """Verifies we don't propagate a given exception during upload."""
156 with cros_test_lib.LoggingCapturer() as logs:
157 stats.StatsUploader._Upload.side_effect = e
158 # Verify the exception is suppressed when error_ok=True
159 stats.StatsUploader.Upload(self.cmd_stats)
160 # Note: the default log level for unit tests is logging.DEBUG
161 self.AssertLogsContain(logs, msg)
163 def testUploadTimeoutIgnore(self):
164 """We don't propagate timeouts during upload."""
165 self.CheckSuppressException(
166 timeout_util.TimeoutError(),
167 stats.StatsUploader.TIMEOUT_ERROR
168 % (stats.StatsUploader.UPLOAD_TIMEOUT,))
170 def testEnvironmentErrorIgnore(self):
171 """We don't propagate any environment errors during upload."""
172 url = 'http://somedomainhere.com/foo/bar/uploader'
173 env_msg = stats.StatsUploader.ENVIRONMENT_ERROR
174 url_msg = stats.StatsUploader.HTTPURL_ERROR % url
175 self.CheckSuppressException(EnvironmentError(), env_msg)
176 self.CheckSuppressException(urllib2.HTTPError(url, None, None, None, None),
178 self.CheckSuppressException(urllib2.URLError(""), env_msg)
180 def testKeyboardInterruptError(self):
181 """We propagate KeyboardInterrupts."""
182 stats.StatsUploader._Upload.side_effect = KeyboardInterrupt()
183 # Verify the exception is suppressed when error_ok=True
184 self.assertRaises(KeyboardInterrupt, stats.StatsUploader.Upload,
187 def testUploadTimeout(self):
188 """We timeout when the upload takes too long."""
189 def Sleep(*_args, **_kwargs):
190 time.sleep(stats.StatsUploader.UPLOAD_TIMEOUT)
192 stats.StatsUploader._Upload.side_effect = Sleep
193 with cros_test_lib.LoggingCapturer() as logs:
194 stats.StatsUploader.Upload(self.cmd_stats, timeout=1)
195 self.AssertLogsContain(logs, stats.StatsUploader.TIMEOUT_ERROR % ('1',))
198 class UploadContextTest(cros_test_lib.MockLoggingTestCase):
199 """Test the suppression behavior of the upload context."""
202 self.StartPatcher(StatsModuleMock())
204 def testNoErrors(self):
205 """Test that we don't print anything when there are no errors."""
206 with cros_test_lib.LoggingCapturer() as logs:
207 with stats.UploadContext() as queue:
208 queue.put([stats.Stats()])
209 self.AssertLogsContain(logs, stats.UNCAUGHT_UPLOAD_ERROR, inverted=True)
210 self.assertEquals(stats.StatsUploader._Upload.call_count, 1)
212 def testErrorSupression(self):
213 """"Test exception supression."""
214 for e in [parallel.BackgroundFailure]:
215 with cros_test_lib.LoggingCapturer() as logs:
216 with stats.UploadContext():
218 self.AssertLogsContain(logs, stats.UNCAUGHT_UPLOAD_ERROR)
220 def testErrorPropagation(self):
221 """Test we propagate some exceptions."""
223 with stats.UploadContext():
226 for e in [KeyboardInterrupt, RuntimeError, Exception, BaseException,
228 self.assertRaises(e, RaiseContext, e)
231 if __name__ == '__main__':
232 cros_test_lib.main(level=logging.DEBUG)