baa2ad3179ef294c8de4dc41fb94f19e980704ab
[platform/framework/web/crosswalk.git] / src / third_party / chromite / cbuildbot / stages / release_stages_unittest.py
1 #!/usr/bin/python
2 # Copyright (c) 2012 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.
5
6 """Unittests for build stages."""
7
8 from __future__ import print_function
9
10 import os
11 import sys
12
13 sys.path.insert(0, os.path.abspath('%s/../../..' % os.path.dirname(__file__)))
14 from chromite.cbuildbot.cbuildbot_unittest import BuilderRunMock
15 from chromite.cbuildbot.stages import artifact_stages
16 from chromite.cbuildbot.stages import generic_stages_unittest
17 from chromite.cbuildbot.stages import release_stages
18 from chromite.cbuildbot import failures_lib
19 from chromite.cbuildbot import results_lib
20 from chromite.lib import cros_test_lib
21 from chromite.lib import timeout_util
22
23 from chromite.cbuildbot.stages.generic_stages_unittest import patch
24
25 from chromite.lib.paygen import gspaths
26 from chromite.lib.paygen import paygen_build_lib
27
28 # TODO(build): Finish test wrapper (http://crosbug.com/37517).
29 # Until then, this has to be after the chromite imports.
30 import mock
31
32 # pylint: disable=R0901, W0212
33 class PaygenStageTest(generic_stages_unittest.AbstractStageTest):
34   """Test the PaygenStageStage."""
35   BOT_ID = 'x86-mario-release'
36   RELEASE_TAG = '0.0.1'
37
38   SIGNER_RESULT = """
39     { "status": { "status": "passed" }, "board": "link",
40     "keyset": "link-mp-v4", "type": "recovery", "channel": "stable" }
41     """
42
43   INSNS_URLS_PER_CHANNEL = {
44       'chan1': ['chan1_uri1', 'chan1_uri2'],
45       'chan2': ['chan2_uri1'],
46   }
47
48   def setUp(self):
49     self.StartPatcher(BuilderRunMock())
50     self._Prepare()
51
52   def ConstructStage(self):
53     archive_stage = artifact_stages.ArchiveStage(self._run, self._current_board)
54     return release_stages.PaygenStage(self._run, self._current_board,
55                                       archive_stage)
56
57   def testWaitForPushImageSuccess(self):
58     """Test waiting for input from PushImage."""
59     stage = self.ConstructStage()
60     stage.board_runattrs.SetParallel(
61         'instruction_urls_per_channel', self.INSNS_URLS_PER_CHANNEL)
62
63     self.assertEqual(stage._WaitForPushImage(), self.INSNS_URLS_PER_CHANNEL)
64
65   def testWaitForPushImageError(self):
66     """Test WaitForPushImageError with an error output from pushimage."""
67     stage = self.ConstructStage()
68     stage.board_runattrs.SetParallel(
69         'instruction_urls_per_channel', None)
70
71     self.assertRaises(release_stages.MissingInstructionException,
72                       stage._WaitForPushImage)
73
74   def testWaitForSigningResultsSuccess(self):
75     """Test that _WaitForSigningResults works when signing works."""
76     results = ['chan1_uri1.json', 'chan1_uri2.json', 'chan2_uri1.json']
77
78     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
79       mock_gs_ctx = mock_gs_ctx_init.return_value
80       mock_gs_ctx.Cat.return_value.output = self.SIGNER_RESULT
81       notifier = mock.Mock()
82
83       stage = self.ConstructStage()
84       stage._WaitForSigningResults(self.INSNS_URLS_PER_CHANNEL, notifier)
85
86       self.assertEqual(notifier.mock_calls,
87                        [mock.call('chan1'),
88                         mock.call('chan2')])
89
90       for result in results:
91         mock_gs_ctx.Cat.assert_any_call(result)
92
93   def testWaitForSigningResultsSuccessNothingSigned(self):
94     """Test _WaitForSigningResults when there are no signed images."""
95     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
96       mock_gs_ctx = mock_gs_ctx_init.return_value
97       mock_gs_ctx.Cat.return_value.output = self.SIGNER_RESULT
98       notifier = mock.Mock()
99
100       stage = self.ConstructStage()
101       stage._WaitForSigningResults({}, notifier)
102
103       self.assertEqual(notifier.mock_calls, [])
104       self.assertEqual(mock_gs_ctx.Cat.mock_calls, [])
105
106   def testWaitForSigningResultsFailure(self):
107     """Test _WaitForSigningResults when the signers report an error."""
108     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
109       mock_gs_ctx = mock_gs_ctx_init.return_value
110       mock_gs_ctx.Cat.return_value.output = """
111           { "status": { "status": "failed" }, "board": "link",
112             "keyset": "link-mp-v4", "type": "recovery", "channel": "stable" }
113           """
114       notifier = mock.Mock()
115
116       stage = self.ConstructStage()
117
118       self.assertRaises(release_stages.SignerFailure,
119                         stage._WaitForSigningResults,
120                         {'chan1': ['chan1_uri1']}, notifier)
121
122       self.assertEqual(notifier.mock_calls, [])
123       self.assertEqual(mock_gs_ctx.Cat.mock_calls,
124                        [mock.call('chan1_uri1.json')])
125
126   def testWaitForSigningResultsMalformedJson(self):
127     """Test _WaitForSigningResults when invalid Json is received.."""
128     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
129       mock_gs_ctx = mock_gs_ctx_init.return_value
130       mock_gs_ctx.Cat.return_value.output = "{"
131       notifier = mock.Mock()
132
133       stage = self.ConstructStage()
134
135       self.assertRaises(release_stages.MalformedResultsException,
136                         stage._WaitForSigningResults,
137                         self.INSNS_URLS_PER_CHANNEL, notifier)
138
139       self.assertEqual(notifier.mock_calls, [])
140       self.assertEqual(mock_gs_ctx.Cat.mock_calls,
141                        [mock.call('chan1_uri1.json')])
142
143   def testWaitForSigningResultsTimeout(self):
144     """Test that _WaitForSigningResults reports timeouts correctly."""
145     with patch(release_stages.timeout_util, 'WaitForSuccess') as mock_wait:
146       mock_wait.side_effect = timeout_util.TimeoutError
147       notifier = mock.Mock()
148
149       stage = self.ConstructStage()
150
151       self.assertRaises(release_stages.SignerResultsTimeout,
152                         stage._WaitForSigningResults,
153                         {'chan1': ['chan1_uri1']}, notifier)
154
155       self.assertEqual(notifier.mock_calls, [])
156
157   def testCheckForResultsSuccess(self):
158     """Test that _CheckForResults works when signing works."""
159     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
160       mock_gs_ctx = mock_gs_ctx_init.return_value
161       mock_gs_ctx.Cat.return_value.output = self.SIGNER_RESULT
162       notifier = mock.Mock()
163
164       stage = self.ConstructStage()
165       self.assertTrue(
166           stage._CheckForResults(mock_gs_ctx,
167                                  self.INSNS_URLS_PER_CHANNEL,
168                                  notifier))
169       self.assertEqual(notifier.mock_calls,
170                        [mock.call('chan1'), mock.call('chan2')])
171
172   def testCheckForResultsSuccessNoChannels(self):
173     """Test that _CheckForResults works when there is nothing to check for."""
174     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
175       mock_gs_ctx = mock_gs_ctx_init.return_value
176       notifier = mock.Mock()
177
178       stage = self.ConstructStage()
179
180       # Ensure we find that we are ready if there are no channels to look for.
181       self.assertTrue(stage._CheckForResults(mock_gs_ctx, {}, notifier))
182
183       # Ensure we didn't contact GS while checking for no channels.
184       self.assertFalse(mock_gs_ctx.Cat.called)
185       self.assertEqual(notifier.mock_calls, [])
186
187   def testCheckForResultsPartialComplete(self):
188     """Verify _CheckForResults handles partial signing results."""
189     def catChan2Success(url):
190       if url.startswith('chan2'):
191         result = mock.Mock()
192         result.output = self.SIGNER_RESULT
193         return result
194       else:
195         raise release_stages.gs.GSNoSuchKey()
196
197     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
198       mock_gs_ctx = mock_gs_ctx_init.return_value
199       mock_gs_ctx.Cat.side_effect = catChan2Success
200       notifier = mock.Mock()
201
202       stage = self.ConstructStage()
203       self.assertFalse(
204           stage._CheckForResults(mock_gs_ctx,
205                                  self.INSNS_URLS_PER_CHANNEL,
206                                  notifier))
207       self.assertEqual(stage.signing_results, {
208           'chan1': {},
209           'chan2': {
210               'chan2_uri1.json': {
211                   'board': 'link',
212                   'channel': 'stable',
213                   'keyset': 'link-mp-v4',
214                   'status': {'status': 'passed'},
215                   'type': 'recovery'
216               }
217           }
218       })
219       self.assertEqual(notifier.mock_calls, [mock.call('chan2')])
220
221   def testCheckForResultsUnexpectedJson(self):
222     """Verify _CheckForResults handles unexpected Json values."""
223     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
224       mock_gs_ctx = mock_gs_ctx_init.return_value
225       mock_gs_ctx.Cat.return_value.output = "{}"
226       notifier = mock.Mock()
227
228       stage = self.ConstructStage()
229       self.assertFalse(
230           stage._CheckForResults(mock_gs_ctx,
231                                  self.INSNS_URLS_PER_CHANNEL,
232                                  notifier))
233       self.assertEqual(stage.signing_results, {
234           'chan1': {}, 'chan2': {}
235       })
236       self.assertEqual(notifier.mock_calls, [])
237
238   def testCheckForResultsNoResult(self):
239     """Verify _CheckForResults handles missing signer results."""
240     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
241       mock_gs_ctx = mock_gs_ctx_init.return_value
242       mock_gs_ctx.Cat.side_effect = release_stages.gs.GSNoSuchKey
243       notifier = mock.Mock()
244
245       stage = self.ConstructStage()
246       self.assertFalse(
247           stage._CheckForResults(mock_gs_ctx,
248                                  self.INSNS_URLS_PER_CHANNEL,
249                                  notifier))
250       self.assertEqual(stage.signing_results, {
251           'chan1': {}, 'chan2': {}
252       })
253       self.assertEqual(notifier.mock_calls, [])
254
255   def testCheckForResultsFailed(self):
256     """Verify _CheckForResults handles missing signer results."""
257     with patch(release_stages.gs, 'GSContext') as mock_gs_ctx_init:
258       mock_gs_ctx = mock_gs_ctx_init.return_value
259       mock_gs_ctx.Cat.side_effect = release_stages.gs.GSNoSuchKey
260       notifier = mock.Mock()
261
262       stage = self.ConstructStage()
263       self.assertFalse(
264           stage._CheckForResults(mock_gs_ctx,
265                                  self.INSNS_URLS_PER_CHANNEL,
266                                  notifier))
267       self.assertEqual(stage.signing_results, {
268           'chan1': {}, 'chan2': {}
269       })
270       self.assertEqual(notifier.mock_calls, [])
271
272   def generateNotifyCalls(self, channels):
273     def side_effect(_, notifier):
274       for channel in channels:
275         notifier(channel)
276     return side_effect
277
278   def testPerformStageSuccess(self):
279     """Test that PaygenStage works when signing works."""
280
281     with patch(release_stages.parallel, 'BackgroundTaskRunner') as background:
282       queue = background().__enter__()
283
284       # This patch is only required for external builds with no config data.
285       with patch(paygen_build_lib, 'ValidateBoardConfig'):
286
287         stage = self.ConstructStage()
288
289         with patch(stage, '_WaitForPushImage') as wait_push:
290           with patch(stage, '_WaitForSigningResults') as wait_signing:
291             wait_push.return_value = self.INSNS_URLS_PER_CHANNEL
292             wait_signing.side_effect = self.generateNotifyCalls(('stable',
293                                                                  'beta'))
294             stage.PerformStage()
295
296         # Verify that we queue up work
297         self.assertEqual(
298             queue.put.call_args_list,
299             [mock.call(('stable', 'x86-mario', '0.0.1', False, False, False)),
300              mock.call(('beta', 'x86-mario', '0.0.1', False, False, False))])
301
302   def testPerformStageSuccessVarientBoard(self):
303     """Test that SignerResultsStage works with varient boards.
304
305     Varient boards need some name conversion. Make sure that's okay.
306     """
307     self._current_board = 'x86-alex_he'
308
309     with patch(release_stages.parallel, 'BackgroundTaskRunner') as background:
310       queue = background().__enter__()
311
312       # This patch is only required for external builds with no config data.
313       with patch(paygen_build_lib, 'ValidateBoardConfig'):
314         stage = self.ConstructStage()
315
316         with patch(stage, '_WaitForPushImage') as wait_push:
317           with patch(stage, '_WaitForSigningResults') as wait_signing:
318             wait_push.return_value = self.INSNS_URLS_PER_CHANNEL
319             wait_signing.side_effect = self.generateNotifyCalls(('stable',
320                                                                  'beta'))
321             stage.PerformStage()
322
323         # Verify that we queue up work
324         self.assertEqual(
325             queue.put.call_args_list,
326             [mock.call(('stable', 'x86-alex-he', '0.0.1', False, False, False)),
327              mock.call(('beta', 'x86-alex-he', '0.0.1', False, False, False))])
328
329   def testPerformStageSigningFailed(self):
330     """Test that PaygenStage works when signing works."""
331     with patch(release_stages.parallel, 'BackgroundTaskRunner') as background:
332       queue = background().__enter__()
333
334       # This patch is only required for external builds with no config data.
335       with patch(paygen_build_lib, 'ValidateBoardConfig'):
336         stage = self.ConstructStage()
337
338         with patch(stage, '_WaitForPushImage') as wait_push:
339           with patch(stage, '_WaitForSigningResults') as wait_signing:
340             wait_push.return_value = self.INSNS_URLS_PER_CHANNEL
341             wait_signing.side_effect = release_stages.SignerFailure
342
343             self.assertRaises(release_stages.SignerFailure,
344                               stage.PerformStage)
345
346         # Ensure no work was queued up.
347         self.assertFalse(queue.put.called)
348
349   def testPerformStageBackgroundFail(self):
350     """Test that exception from background processes are properly handled."""
351     with patch(paygen_build_lib, 'CreatePayloads') as create_payloads:
352       create_payloads.side_effect = failures_lib.TestLabFailure
353
354       # This patch is only required for external builds with no config data.
355       with patch(paygen_build_lib, 'ValidateBoardConfig'):
356         stage = release_stages.PaygenStage(
357             self._run, self._current_board,
358             archive_stage=None, channels=['foo', 'bar'])
359
360         with patch(stage, '_HandleExceptionAsWarning') as warning_handler:
361           warning_handler.return_value = (results_lib.Results.FORGIVEN,
362                                           'description',
363                                           0)
364
365           stage.Run()
366
367           # This proves the exception was turned into a warning.
368           self.assertTrue(warning_handler.called)
369
370   def testPerformStageTrybot(self):
371     """Test the PerformStage alternate behavior for trybot runs."""
372     with patch(release_stages.parallel, 'BackgroundTaskRunner') as background:
373       queue = background().__enter__()
374
375       # This patch is only required for external builds with no config data.
376       with patch(paygen_build_lib, 'ValidateBoardConfig'):
377         # The stage is constructed differently for trybots, so don't use
378         # ConstructStage.
379         stage = release_stages.PaygenStage(self._run, self._current_board,
380                                    archive_stage=None, channels=['foo', 'bar'])
381         with patch(stage, '_WaitForPushImage') as wait_push:
382           with patch(stage, '_WaitForSigningResults') as wait_signing:
383             stage.PerformStage()
384
385           # Make sure we don't wait on push_image or signing in this case.
386           self.assertEqual(wait_push.mock_calls, [])
387           self.assertEqual(wait_signing.mock_calls, [])
388
389         # Notice that we didn't put anything in _wait_for_channel_signing, but
390         # still got results right away.
391         self.assertEqual(
392             queue.put.call_args_list,
393             [mock.call(('foo', 'x86-mario', '0.0.1', False, False, False)),
394              mock.call(('bar', 'x86-mario', '0.0.1', False, False, False))])
395
396   def testPerformStageUnknownBoard(self):
397     """Test that PaygenStage exits when an unknown board is specified."""
398     self._current_board = 'unknown-board-name'
399
400     badBoardException = paygen_build_lib.BoardNotConfigured(self._current_board)
401
402     # This patch is only required for external builds with no config data.
403     with patch(paygen_build_lib, 'ValidateBoardConfig') as validate_boards:
404       validate_boards.side_effect = badBoardException
405
406       stage = self.ConstructStage()
407
408       self.assertRaises(release_stages.PaygenNoPaygenConfigForBoard,
409                         stage.PerformStage)
410
411   def testRunPaygenInProcess(self):
412     """Test that _RunPaygenInProcess works in the simple case."""
413     with patch(paygen_build_lib, 'CreatePayloads') as create_payloads:
414       # Call the method under test.
415       stage = self.ConstructStage()
416       stage._RunPaygenInProcess('foo', 'foo-board', 'foo-version',
417                                 False, False, False)
418
419       # Ensure arguments are properly converted and passed along.
420       create_payloads.assert_called_with(gspaths.Build(version='foo-version',
421                                                        board='foo-board',
422                                                        channel='foo-channel'),
423                                          dry_run=False,
424                                          work_dir=mock.ANY,
425                                          run_parallel=True,
426                                          run_on_builder=True,
427                                          skip_delta_payloads=False,
428                                          skip_test_payloads=False,
429                                          skip_autotest=False)
430
431   def testRunPaygenInProcessComplex(self):
432     """Test that _RunPaygenInProcess with arguments that are more unusual."""
433     with patch(paygen_build_lib, 'CreatePayloads') as create_payloads:
434       # Call the method under test.
435       # Use release tools channel naming, and a board name including a variant.
436       stage = self.ConstructStage()
437       stage._RunPaygenInProcess('foo-channel', 'foo-board-variant',
438                                 'foo-version', True, True, True)
439
440       # Ensure arguments are properly converted and passed along.
441       create_payloads.assert_called_with(
442           gspaths.Build(version='foo-version',
443                         board='foo-board-variant',
444                         channel='foo-channel'),
445           dry_run=True,
446           work_dir=mock.ANY,
447           run_parallel=True,
448           run_on_builder=True,
449           skip_delta_payloads=True,
450           skip_test_payloads=True,
451           skip_autotest=True)
452
453 if __name__ == '__main__':
454   cros_test_lib.main()