blob: d497bf89052d6eccb7f9da6b11ecac233c67e0ac [file] [log] [blame]
Mike Frysingerd03e6b52019-08-03 12:49:01 -04001#!/usr/bin/env python2
J. Richard Barnette96db3492015-03-27 17:23:52 -07002# Copyright 2015 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
Prathmesh Prabhu58aede82017-11-09 13:34:25 -08006import collections
J. Richard Barnettef6839282015-06-01 16:00:35 -07007import itertools
J. Richard Barnettef60a1ee2015-06-02 10:52:37 -07008import logging
J. Richard Barnette96db3492015-03-27 17:23:52 -07009import os
10import unittest
11
12import common
Richard Barnettea3071b72018-09-26 10:04:18 -070013from autotest_lib.frontend.afe.json_rpc import proxy
Aviv Keshet7ee95862016-08-30 15:18:27 -070014from autotest_lib.server.lib import status_history
J. Richard Barnette96db3492015-03-27 17:23:52 -070015from autotest_lib.site_utils import lab_inventory
J. Richard Barnette96db3492015-03-27 17:23:52 -070016
17
Richard Barnette59404262018-09-14 15:25:30 -070018# _FAKE_TIME - an arbitrary but plausible time_t value.
19# You can make your own with `date +%s`.
20
21_FAKE_TIME = 1537457599
22
23
xixuan12ce04f2016-03-10 13:16:30 -080024class _FakeHost(object):
25 """Class to mock `Host` in _FakeHostHistory for testing."""
26
Richard Barnette5de01eb2017-12-15 09:53:42 -080027 def __init__(self, hostname):
xixuan12ce04f2016-03-10 13:16:30 -080028 self.hostname = hostname
29
30
Richard Barnette59404262018-09-14 15:25:30 -070031class _FakeHostEvent(object):
32 def __init__(self, time):
33 self.start_time = time
34 self.end_time = time + 1
35
36
J. Richard Barnette96db3492015-03-27 17:23:52 -070037class _FakeHostHistory(object):
38 """Class to mock `HostJobHistory` for testing."""
39
Richard Barnette5de01eb2017-12-15 09:53:42 -080040 def __init__(self, model, pool, status, hostname=''):
41 self.host_model = model
42 self.host_board = model + '_board'
Prathmesh Prabhu58aede82017-11-09 13:34:25 -080043 self.host_pool = pool
44 self.status = status
Richard Barnette5de01eb2017-12-15 09:53:42 -080045 self.host = _FakeHost(hostname)
Jacob Kopczynski7c4c9542018-08-13 17:24:41 -070046 self.hostname = hostname
Richard Barnette59404262018-09-14 15:25:30 -070047 self.start_time = _FAKE_TIME
48 self.end_time = _FAKE_TIME + 20
49 self.fake_task = _FakeHostEvent(_FAKE_TIME + 5)
Richard Barnettea3071b72018-09-26 10:04:18 -070050 self.exception = None
J. Richard Barnette96db3492015-03-27 17:23:52 -070051
J. Richard Barnette96db3492015-03-27 17:23:52 -070052 def last_diagnosis(self):
53 """Return the recorded diagnosis."""
Richard Barnettea3071b72018-09-26 10:04:18 -070054 if self.exception:
55 raise self.exception
56 else:
57 return self.status, self.fake_task
J. Richard Barnette96db3492015-03-27 17:23:52 -070058
59
J. Richard Barnettef6839282015-06-01 16:00:35 -070060class _FakeHostLocation(object):
61 """Class to mock `HostJobHistory` for location sorting."""
62
63 _HOSTNAME_FORMAT = 'chromeos%d-row%d-rack%d-host%d'
64
J. Richard Barnettef6839282015-06-01 16:00:35 -070065 def __init__(self, location):
66 self.hostname = self._HOSTNAME_FORMAT % location
67
J. Richard Barnettef6839282015-06-01 16:00:35 -070068 @property
69 def host(self):
70 """Return a fake host object with a hostname."""
71 return self
72
73
J. Richard Barnette96db3492015-03-27 17:23:52 -070074# Status values that may be returned by `HostJobHistory`.
75#
Richard Barnette5de01eb2017-12-15 09:53:42 -080076# These merely rename the corresponding values in `status_history`
77# for convenience.
J. Richard Barnette96db3492015-03-27 17:23:52 -070078
79_WORKING = status_history.WORKING
Richard Barnette5de01eb2017-12-15 09:53:42 -080080_UNUSED = status_history.UNUSED
81_BROKEN = status_history.BROKEN
82_UNKNOWN = status_history.UNKNOWN
J. Richard Barnette96db3492015-03-27 17:23:52 -070083
84
Richard Barnette59404262018-09-14 15:25:30 -070085class GetStatusTestCase(unittest.TestCase):
86 """Tests for `_get_diagnosis()`."""
87
88 def _get_diagnosis_status(self, history):
89 return lab_inventory._get_diagnosis(history).status
90
91 def test_working_and_in_range(self):
92 """Test WORKING when task times are in the history range."""
93 history = _FakeHostHistory('', '', _WORKING)
94 history.fake_task = _FakeHostEvent(history.start_time + 1)
95 self.assertEqual(self._get_diagnosis_status(history), _WORKING)
96
97 def test_broken_and_in_range(self):
98 """Test BROKEN when task times are in the history range."""
99 history = _FakeHostHistory('', '', _BROKEN)
100 history.fake_task = _FakeHostEvent(history.start_time + 1)
101 self.assertEqual(self._get_diagnosis_status(history), _BROKEN)
102
103 def test_broken_and_straddles(self):
104 """Test BROKEN when task time straddles the history start point."""
105 history = _FakeHostHistory('', '', _BROKEN)
106 history.fake_task = _FakeHostEvent(history.start_time - 1)
107 self.assertEqual(self._get_diagnosis_status(history), _BROKEN)
108
109 def test_broken_and_out_of_range(self):
110 """Test BROKEN when task times are before the history range."""
111 history = _FakeHostHistory('', '', _BROKEN)
112 history.fake_task = _FakeHostEvent(history.start_time - 2)
113 self.assertEqual(self._get_diagnosis_status(history), _UNUSED)
114
Richard Barnettea3071b72018-09-26 10:04:18 -0700115 def test_exception(self):
116 """Test exceptions raised by `last_diagnosis()`."""
117 history = _FakeHostHistory('', '', _BROKEN)
118 history.exception = proxy.JSONRPCException('exception for testing')
119 self.assertIsNone(self._get_diagnosis_status(history))
120
Richard Barnette59404262018-09-14 15:25:30 -0700121
Richard Barnette5de01eb2017-12-15 09:53:42 -0800122class HostSetInventoryTestCase(unittest.TestCase):
123 """Unit tests for class `_HostSetInventory`.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700124
125 Coverage is quite basic: mostly just enough to make sure every
126 function gets called, and to make sure that the counting knows
127 the difference between 0 and 1.
128
Richard Barnette5de01eb2017-12-15 09:53:42 -0800129 The testing also ensures that all known status values that can be
130 returned by `HostJobHistory` are counted as expected.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700131 """
132
133 def setUp(self):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800134 super(HostSetInventoryTestCase, self).setUp()
135 self.histories = lab_inventory._HostSetInventory()
J. Richard Barnette96db3492015-03-27 17:23:52 -0700136
J. Richard Barnette96db3492015-03-27 17:23:52 -0700137 def _add_host(self, status):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800138 fake = _FakeHostHistory('zebra', lab_inventory.SPARE_POOL, status)
Prathmesh Prabhu0ecbf322017-11-08 17:04:24 -0800139 self.histories.record_host(fake)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700140
xixuan12ce04f2016-03-10 13:16:30 -0800141 def _check_counts(self, working, broken, idle):
J. Richard Barnette96db3492015-03-27 17:23:52 -0700142 """Check that pool counts match expectations.
143
Richard Barnette5de01eb2017-12-15 09:53:42 -0800144 Asserts that `get_working()`, `get_broken()`, and `get_idle()`
145 return the given expected values. Also assert that
146 `get_total()` is the sum of all counts.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700147
148 @param working The expected total of working devices.
149 @param broken The expected total of broken devices.
Richard Barnette5de01eb2017-12-15 09:53:42 -0800150 @param idle The expected total of idle devices.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700151 """
Prathmesh Prabhu0ecbf322017-11-08 17:04:24 -0800152 self.assertEqual(self.histories.get_working(), working)
153 self.assertEqual(self.histories.get_broken(), broken)
154 self.assertEqual(self.histories.get_idle(), idle)
155 self.assertEqual(self.histories.get_total(),
xixuan12ce04f2016-03-10 13:16:30 -0800156 working + broken + idle)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700157
J. Richard Barnette96db3492015-03-27 17:23:52 -0700158 def test_empty(self):
159 """Test counts when there are no DUTs recorded."""
xixuan12ce04f2016-03-10 13:16:30 -0800160 self._check_counts(0, 0, 0)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700161
xixuan12ce04f2016-03-10 13:16:30 -0800162 def test_broken(self):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800163 """Test counting for broken DUTs."""
xixuan12ce04f2016-03-10 13:16:30 -0800164 self._add_host(_BROKEN)
165 self._check_counts(0, 1, 0)
166
Richard Barnette5de01eb2017-12-15 09:53:42 -0800167 def test_working(self):
168 """Test counting for working DUTs."""
169 self._add_host(_WORKING)
170 self._check_counts(1, 0, 0)
171
xixuan12ce04f2016-03-10 13:16:30 -0800172 def test_idle(self):
173 """Testing counting for idle status values."""
174 self._add_host(_UNUSED)
175 self._check_counts(0, 0, 1)
176 self._add_host(_UNKNOWN)
177 self._check_counts(0, 0, 2)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700178
J. Richard Barnette96db3492015-03-27 17:23:52 -0700179 def test_working_then_broken(self):
180 """Test counts after adding a working and then a broken DUT."""
181 self._add_host(_WORKING)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700182 self._add_host(_BROKEN)
xixuan12ce04f2016-03-10 13:16:30 -0800183 self._check_counts(1, 1, 0)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700184
J. Richard Barnette96db3492015-03-27 17:23:52 -0700185 def test_broken_then_working(self):
186 """Test counts after adding a broken and then a working DUT."""
187 self._add_host(_BROKEN)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700188 self._add_host(_WORKING)
xixuan12ce04f2016-03-10 13:16:30 -0800189 self._check_counts(1, 1, 0)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700190
191
Richard Barnette5de01eb2017-12-15 09:53:42 -0800192class PoolSetInventoryTestCase(unittest.TestCase):
193 """Unit tests for class `_PoolSetInventory`.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700194
Richard Barnette5de01eb2017-12-15 09:53:42 -0800195 Coverage is quite basic: just enough to make sure every function
196 gets called, and to make sure that the counting knows the difference
197 between 0 and 1.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700198
Richard Barnette5de01eb2017-12-15 09:53:42 -0800199 The tests make sure that both individual pool counts and totals are
200 counted correctly.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700201 """
202
Richard Barnette5de01eb2017-12-15 09:53:42 -0800203 _POOL_SET = ['humpty', 'dumpty']
204
J. Richard Barnette96db3492015-03-27 17:23:52 -0700205 def setUp(self):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800206 super(PoolSetInventoryTestCase, self).setUp()
207 self._pool_histories = lab_inventory._PoolSetInventory(self._POOL_SET)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700208
J. Richard Barnette96db3492015-03-27 17:23:52 -0700209 def _add_host(self, pool, status):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800210 fake = _FakeHostHistory('zebra', pool, status)
211 self._pool_histories.record_host(fake)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700212
J. Richard Barnette96db3492015-03-27 17:23:52 -0700213 def _check_all_counts(self, working, broken):
214 """Check that total counts for all pools match expectations.
215
216 Checks that `get_working()` and `get_broken()` return the
217 given expected values when called without a pool specified.
218 Also check that `get_total()` is the sum of working and
219 broken devices.
220
221 Additionally, call the various functions for all the pools
222 individually, and confirm that the totals across pools match
223 the given expectations.
224
225 @param working The expected total of working devices.
226 @param broken The expected total of broken devices.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700227 """
Richard Barnette5de01eb2017-12-15 09:53:42 -0800228 self.assertEqual(self._pool_histories.get_working(), working)
229 self.assertEqual(self._pool_histories.get_broken(), broken)
230 self.assertEqual(self._pool_histories.get_total(),
J. Richard Barnette96db3492015-03-27 17:23:52 -0700231 working + broken)
232 count_working = 0
233 count_broken = 0
234 count_total = 0
Richard Barnette5de01eb2017-12-15 09:53:42 -0800235 for pool in self._POOL_SET:
236 count_working += self._pool_histories.get_working(pool)
237 count_broken += self._pool_histories.get_broken(pool)
238 count_total += self._pool_histories.get_total(pool)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700239 self.assertEqual(count_working, working)
240 self.assertEqual(count_broken, broken)
241 self.assertEqual(count_total, working + broken)
242
J. Richard Barnette96db3492015-03-27 17:23:52 -0700243 def _check_pool_counts(self, pool, working, broken):
244 """Check that counts for a given pool match expectations.
245
246 Checks that `get_working()` and `get_broken()` return the
247 given expected values for the given pool. Also check that
248 `get_total()` is the sum of working and broken devices.
249
250 @param pool The pool to be checked.
251 @param working The expected total of working devices.
252 @param broken The expected total of broken devices.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700253 """
Richard Barnette5de01eb2017-12-15 09:53:42 -0800254 self.assertEqual(self._pool_histories.get_working(pool),
J. Richard Barnette96db3492015-03-27 17:23:52 -0700255 working)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800256 self.assertEqual(self._pool_histories.get_broken(pool),
J. Richard Barnette96db3492015-03-27 17:23:52 -0700257 broken)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800258 self.assertEqual(self._pool_histories.get_total(pool),
J. Richard Barnette96db3492015-03-27 17:23:52 -0700259 working + broken)
260
J. Richard Barnette96db3492015-03-27 17:23:52 -0700261 def test_empty(self):
262 """Test counts when there are no DUTs recorded."""
263 self._check_all_counts(0, 0)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800264 for pool in self._POOL_SET:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700265 self._check_pool_counts(pool, 0, 0)
266
J. Richard Barnette96db3492015-03-27 17:23:52 -0700267 def test_all_working_then_broken(self):
268 """Test counts after adding a working and then a broken DUT.
269
270 For each pool, add first a working, then a broken DUT. After
271 each DUT is added, check counts to confirm the correct values.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700272 """
273 working = 0
274 broken = 0
Richard Barnette5de01eb2017-12-15 09:53:42 -0800275 for pool in self._POOL_SET:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700276 self._add_host(pool, _WORKING)
277 working += 1
278 self._check_pool_counts(pool, 1, 0)
279 self._check_all_counts(working, broken)
280 self._add_host(pool, _BROKEN)
281 broken += 1
282 self._check_pool_counts(pool, 1, 1)
283 self._check_all_counts(working, broken)
284
J. Richard Barnette96db3492015-03-27 17:23:52 -0700285 def test_all_broken_then_working(self):
286 """Test counts after adding a broken and then a working DUT.
287
288 For each pool, add first a broken, then a working DUT. After
289 each DUT is added, check counts to confirm the correct values.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700290 """
291 working = 0
292 broken = 0
Richard Barnette5de01eb2017-12-15 09:53:42 -0800293 for pool in self._POOL_SET:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700294 self._add_host(pool, _BROKEN)
295 broken += 1
296 self._check_pool_counts(pool, 0, 1)
297 self._check_all_counts(working, broken)
298 self._add_host(pool, _WORKING)
299 working += 1
300 self._check_pool_counts(pool, 1, 1)
301 self._check_all_counts(working, broken)
302
303
J. Richard Barnettef6839282015-06-01 16:00:35 -0700304class LocationSortTests(unittest.TestCase):
305 """Unit tests for `_sort_by_location()`."""
306
307 def setUp(self):
308 super(LocationSortTests, self).setUp()
309
J. Richard Barnettef6839282015-06-01 16:00:35 -0700310 def _check_sorting(self, *locations):
311 """Test sorting a given list of locations.
312
313 The input is an already ordered list of lists of tuples with
314 row, rack, and host numbers. The test converts the tuples
315 to hostnames, preserving the original ordering. Then it
316 flattens and scrambles the input, runs it through
317 `_sort_by_location()`, and asserts that the result matches
318 the original.
J. Richard Barnettef6839282015-06-01 16:00:35 -0700319 """
320 lab = 0
321 expected = []
322 for tuples in locations:
323 lab += 1
324 expected.append(
325 [_FakeHostLocation((lab,) + t) for t in tuples])
326 scrambled = [e for e in itertools.chain(*expected)]
327 scrambled = [e for e in reversed(scrambled)]
328 actual = lab_inventory._sort_by_location(scrambled)
329 # The ordering of the labs in the output isn't guaranteed,
330 # so we can't compare `expected` and `actual` directly.
331 # Instead, we create a dictionary keyed on the first host in
332 # each lab, and compare the dictionaries.
333 self.assertEqual({l[0]: l for l in expected},
334 {l[0]: l for l in actual})
335
J. Richard Barnettef6839282015-06-01 16:00:35 -0700336 def test_separate_labs(self):
337 """Test that sorting distinguishes labs."""
338 self._check_sorting([(1, 1, 1)], [(1, 1, 1)], [(1, 1, 1)])
339
J. Richard Barnettef6839282015-06-01 16:00:35 -0700340 def test_separate_rows(self):
341 """Test for proper sorting when only rows are different."""
342 self._check_sorting([(1, 1, 1), (9, 1, 1), (10, 1, 1)])
343
J. Richard Barnettef6839282015-06-01 16:00:35 -0700344 def test_separate_racks(self):
345 """Test for proper sorting when only racks are different."""
346 self._check_sorting([(1, 1, 1), (1, 9, 1), (1, 10, 1)])
347
J. Richard Barnettef6839282015-06-01 16:00:35 -0700348 def test_separate_hosts(self):
349 """Test for proper sorting when only hosts are different."""
350 self._check_sorting([(1, 1, 1), (1, 1, 9), (1, 1, 10)])
351
J. Richard Barnettef6839282015-06-01 16:00:35 -0700352 def test_diagonal(self):
353 """Test for proper sorting when all parts are different."""
354 self._check_sorting([(1, 1, 2), (1, 2, 1), (2, 1, 1)])
355
356
357class InventoryScoringTests(unittest.TestCase):
358 """Unit tests for `_score_repair_set()`."""
359
360 def setUp(self):
361 super(InventoryScoringTests, self).setUp()
362
J. Richard Barnettef6839282015-06-01 16:00:35 -0700363 def _make_buffer_counts(self, *counts):
364 """Create a dictionary suitable as `buffer_counts`.
365
Richard Barnette5de01eb2017-12-15 09:53:42 -0800366 @param counts List of tuples with model count data.
J. Richard Barnettef6839282015-06-01 16:00:35 -0700367 """
368 self._buffer_counts = dict(counts)
369
J. Richard Barnettef6839282015-06-01 16:00:35 -0700370 def _make_history_list(self, repair_counts):
371 """Create a list suitable as `repair_list`.
372
Richard Barnette5de01eb2017-12-15 09:53:42 -0800373 @param repair_counts List of (model, count) tuples.
J. Richard Barnettef6839282015-06-01 16:00:35 -0700374 """
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700375 pool = lab_inventory.SPARE_POOL
J. Richard Barnettef6839282015-06-01 16:00:35 -0700376 histories = []
Richard Barnette5de01eb2017-12-15 09:53:42 -0800377 for model, count in repair_counts:
J. Richard Barnettef6839282015-06-01 16:00:35 -0700378 for i in range(0, count):
379 histories.append(
Richard Barnette5de01eb2017-12-15 09:53:42 -0800380 _FakeHostHistory(model, pool, _BROKEN))
J. Richard Barnettef6839282015-06-01 16:00:35 -0700381 return histories
382
J. Richard Barnettef6839282015-06-01 16:00:35 -0700383 def _check_better(self, repair_a, repair_b):
384 """Test that repair set A scores better than B.
385
386 Contruct repair sets from `repair_a` and `repair_b`,
387 and score both of them using the pre-existing
388 `self._buffer_counts`. Assert that the score for A is
389 better than the score for B.
390
391 @param repair_a Input data for repair set A
392 @param repair_b Input data for repair set B
J. Richard Barnettef6839282015-06-01 16:00:35 -0700393 """
394 score_a = lab_inventory._score_repair_set(
395 self._buffer_counts,
396 self._make_history_list(repair_a))
397 score_b = lab_inventory._score_repair_set(
398 self._buffer_counts,
399 self._make_history_list(repair_b))
400 self.assertGreater(score_a, score_b)
401
J. Richard Barnettef6839282015-06-01 16:00:35 -0700402 def _check_equal(self, repair_a, repair_b):
403 """Test that repair set A scores the same as B.
404
405 Contruct repair sets from `repair_a` and `repair_b`,
406 and score both of them using the pre-existing
407 `self._buffer_counts`. Assert that the score for A is
408 equal to the score for B.
409
410 @param repair_a Input data for repair set A
411 @param repair_b Input data for repair set B
J. Richard Barnettef6839282015-06-01 16:00:35 -0700412 """
413 score_a = lab_inventory._score_repair_set(
414 self._buffer_counts,
415 self._make_history_list(repair_a))
416 score_b = lab_inventory._score_repair_set(
417 self._buffer_counts,
418 self._make_history_list(repair_b))
419 self.assertEqual(score_a, score_b)
420
Richard Barnette5de01eb2017-12-15 09:53:42 -0800421 def test_improve_worst_model(self):
422 """Test that improving the worst model improves scoring.
J. Richard Barnettef6839282015-06-01 16:00:35 -0700423
Richard Barnette5de01eb2017-12-15 09:53:42 -0800424 Construct a buffer counts dictionary with all models having
J. Richard Barnettef6839282015-06-01 16:00:35 -0700425 different counts. Assert that it is both necessary and
Richard Barnette5de01eb2017-12-15 09:53:42 -0800426 sufficient to improve the count of the worst model in order
J. Richard Barnettef6839282015-06-01 16:00:35 -0700427 to improve the score.
J. Richard Barnettef6839282015-06-01 16:00:35 -0700428 """
429 self._make_buffer_counts(('lion', 0),
430 ('tiger', 1),
431 ('bear', 2))
432 self._check_better([('lion', 1)], [('tiger', 1)])
433 self._check_better([('lion', 1)], [('bear', 1)])
434 self._check_better([('lion', 1)], [('tiger', 2)])
435 self._check_better([('lion', 1)], [('bear', 2)])
436 self._check_equal([('tiger', 1)], [('bear', 1)])
437
J. Richard Barnettef6839282015-06-01 16:00:35 -0700438 def test_improve_worst_case_count(self):
439 """Test that improving the number of worst cases improves the score.
440
Richard Barnette5de01eb2017-12-15 09:53:42 -0800441 Construct a buffer counts dictionary with all models having
442 the same counts. Assert that improving two models is better
443 than improving one. Assert that improving any one model is
J. Richard Barnettef6839282015-06-01 16:00:35 -0700444 as good as any other.
J. Richard Barnettef6839282015-06-01 16:00:35 -0700445 """
446 self._make_buffer_counts(('lion', 0),
447 ('tiger', 0),
448 ('bear', 0))
449 self._check_better([('lion', 1), ('tiger', 1)], [('bear', 2)])
450 self._check_equal([('lion', 2)], [('tiger', 1)])
451 self._check_equal([('tiger', 1)], [('bear', 1)])
452
453
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800454# Each item is the number of DUTs in that status.
455STATUS_CHOICES = (_WORKING, _BROKEN, _UNUSED)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800456StatusCounts = collections.namedtuple('StatusCounts', ['good', 'bad', 'idle'])
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800457# Each item is a StatusCounts tuple specifying the number of DUTs per status in
458# the that pool.
459CRITICAL_POOL = lab_inventory.CRITICAL_POOLS[0]
460SPARE_POOL = lab_inventory.SPARE_POOL
461POOL_CHOICES = (CRITICAL_POOL, SPARE_POOL)
462PoolStatusCounts = collections.namedtuple('PoolStatusCounts',
463 ['critical', 'spare'])
J. Richard Barnette96db3492015-03-27 17:23:52 -0700464
Richard Barnette5de01eb2017-12-15 09:53:42 -0800465def create_inventory(data):
466 """Create a `_LabInventory` instance for testing.
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800467
Richard Barnette5de01eb2017-12-15 09:53:42 -0800468 This function allows the construction of a complete `_LabInventory`
469 object from a simplified input representation.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700470
471 A single 'critical pool' is arbitrarily chosen for purposes of
472 testing; there's no coverage for testing arbitrary combinations
473 in more than one critical pool.
474
Richard Barnette5de01eb2017-12-15 09:53:42 -0800475 @param data: dict {key: PoolStatusCounts}.
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800476 @returns: lab_inventory._LabInventory object.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700477 """
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800478 histories = []
Richard Barnette5de01eb2017-12-15 09:53:42 -0800479 for model, counts in data.iteritems():
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800480 for p, pool in enumerate(POOL_CHOICES):
481 for s, status in enumerate(STATUS_CHOICES):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800482 fake_host = _FakeHostHistory(model, pool, status)
483 histories.extend([fake_host] * counts[p][s])
484 inventory = lab_inventory._LabInventory(
485 histories, lab_inventory.MANAGED_POOLS)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800486 return inventory
J. Richard Barnette96db3492015-03-27 17:23:52 -0700487
Richard Barnette5de01eb2017-12-15 09:53:42 -0800488
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800489class LabInventoryTests(unittest.TestCase):
J. Richard Barnette96db3492015-03-27 17:23:52 -0700490 """Tests for the basic functions of `_LabInventory`.
491
Richard Barnette5de01eb2017-12-15 09:53:42 -0800492 Contains basic coverage to show that after an inventory is created
493 and DUTs with known status are added, the inventory counts match the
494 counts of the added DUTs.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700495 """
496
Richard Barnette5de01eb2017-12-15 09:53:42 -0800497 _MODEL_LIST = ['lion', 'tiger', 'bear'] # Oh, my!
J. Richard Barnette96db3492015-03-27 17:23:52 -0700498
Richard Barnette5de01eb2017-12-15 09:53:42 -0800499 def _check_inventory_counts(self, inventory, data, msg=None):
500 """Check that all counts in the inventory match `data`.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700501
Richard Barnette5de01eb2017-12-15 09:53:42 -0800502 This asserts that the actual counts returned by the various
503 accessor functions for `inventory` match the values expected for
504 the given `data` that created the inventory.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700505
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800506 @param inventory: _LabInventory object to check.
507 @param data Inventory data to check against. Same type as
508 `create_inventory`.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700509 """
Richard Barnette5de01eb2017-12-15 09:53:42 -0800510 self.assertEqual(set(inventory.keys()), set(data.keys()))
511 for model, histories in inventory.iteritems():
512 expected_counts = data[model]
513 actual_counts = PoolStatusCounts(
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800514 StatusCounts(
515 histories.get_working(CRITICAL_POOL),
516 histories.get_broken(CRITICAL_POOL),
517 histories.get_idle(CRITICAL_POOL),
518 ),
519 StatusCounts(
520 histories.get_working(SPARE_POOL),
521 histories.get_broken(SPARE_POOL),
522 histories.get_idle(SPARE_POOL),
523 ),
524 )
Richard Barnette5de01eb2017-12-15 09:53:42 -0800525 self.assertEqual(actual_counts, expected_counts, msg)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800526
527 self.assertEqual(len(histories.get_working_list()),
Richard Barnette5de01eb2017-12-15 09:53:42 -0800528 sum([p.good for p in expected_counts]),
Prathmesh Prabhua5a0e3d2017-11-09 08:53:53 -0800529 msg)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800530 self.assertEqual(len(histories.get_broken_list()),
Richard Barnette5de01eb2017-12-15 09:53:42 -0800531 sum([p.bad for p in expected_counts]),
Prathmesh Prabhua5a0e3d2017-11-09 08:53:53 -0800532 msg)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800533 self.assertEqual(len(histories.get_idle_list()),
Richard Barnette5de01eb2017-12-15 09:53:42 -0800534 sum([p.idle for p in expected_counts]),
Prathmesh Prabhua5a0e3d2017-11-09 08:53:53 -0800535 msg)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700536
J. Richard Barnette96db3492015-03-27 17:23:52 -0700537 def test_empty(self):
538 """Test counts when there are no DUTs recorded."""
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800539 inventory = create_inventory({})
540 self.assertEqual(inventory.get_num_duts(), 0)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800541 self.assertEqual(inventory.get_boards(), set())
542 self._check_inventory_counts(inventory, {})
Prathmesh Prabhua5a0e3d2017-11-09 08:53:53 -0800543 self.assertEqual(inventory.get_num_models(), 0)
J. Richard Barnette6948ed32015-05-06 08:57:10 -0700544
Richard Barnette5de01eb2017-12-15 09:53:42 -0800545 def _check_model_count(self, model_count):
Prathmesh Prabhua5a0e3d2017-11-09 08:53:53 -0800546 """Parameterized test for testing a specific number of models."""
Richard Barnette5de01eb2017-12-15 09:53:42 -0800547 msg = '[model: %d]' % (model_count,)
548 models = self._MODEL_LIST[:model_count]
Prathmesh Prabhua5a0e3d2017-11-09 08:53:53 -0800549 data = {
550 m: PoolStatusCounts(
551 StatusCounts(1, 1, 1),
552 StatusCounts(1, 1, 1),
553 )
554 for m in models
555 }
Richard Barnette5de01eb2017-12-15 09:53:42 -0800556 inventory = create_inventory(data)
Prathmesh Prabhua5a0e3d2017-11-09 08:53:53 -0800557 self.assertEqual(inventory.get_num_duts(), 6 * model_count, msg)
558 self.assertEqual(inventory.get_num_models(), model_count, msg)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800559 for pool in [CRITICAL_POOL, SPARE_POOL]:
560 self.assertEqual(set(inventory.get_pool_models(pool)),
561 set(models))
562 self._check_inventory_counts(inventory, data, msg=msg)
563
Richard Barnette5de01eb2017-12-15 09:53:42 -0800564 def test_model_counts(self):
565 """Test counts for various numbers of models."""
566 self.longMessage = True
567 for model_count in range(0, len(self._MODEL_LIST)):
568 self._check_model_count(model_count)
569
Richard Barnette5de01eb2017-12-15 09:53:42 -0800570 def _check_single_dut_counts(self, critical, spare):
571 """Parmeterized test for single dut counts."""
572 self.longMessage = True
573 counts = PoolStatusCounts(critical, spare)
574 model = self._MODEL_LIST[0]
575 data = {model: counts}
576 msg = '[data: %s]' % (data,)
577 inventory = create_inventory(data)
578 self.assertEqual(inventory.get_num_duts(), 1, msg)
579 self.assertEqual(inventory.get_num_models(), 1, msg)
580 self._check_inventory_counts(inventory, data, msg=msg)
Prathmesh Prabhua5a0e3d2017-11-09 08:53:53 -0800581
J. Richard Barnette96db3492015-03-27 17:23:52 -0700582 def test_single_dut_counts(self):
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800583 """Test counts when there is a single DUT per board, and it is good."""
Richard Barnette5de01eb2017-12-15 09:53:42 -0800584 status_100 = StatusCounts(1, 0, 0)
585 status_010 = StatusCounts(0, 1, 0)
586 status_001 = StatusCounts(0, 0, 1)
587 status_null = StatusCounts(0, 0, 0)
588 self._check_single_dut_counts(status_100, status_null)
589 self._check_single_dut_counts(status_010, status_null)
590 self._check_single_dut_counts(status_001, status_null)
591 self._check_single_dut_counts(status_null, status_100)
592 self._check_single_dut_counts(status_null, status_010)
593 self._check_single_dut_counts(status_null, status_001)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700594
595
Richard Barnette5de01eb2017-12-15 09:53:42 -0800596# MODEL_MESSAGE_TEMPLATE -
J. Richard Barnette96db3492015-03-27 17:23:52 -0700597# This is a sample of the output text produced by
Richard Barnette5de01eb2017-12-15 09:53:42 -0800598# _generate_model_inventory_message(). This string is parsed by the
J. Richard Barnette96db3492015-03-27 17:23:52 -0700599# tests below to construct a sample inventory that should produce
600# the output, and then the output is generated and checked against
601# this original sample.
602#
603# Constructing inventories from parsed sample text serves two
604# related purposes:
605# - It provides a way to see what the output should look like
606# without having to run the script.
607# - It helps make sure that a human being will actually look at
608# the output to see that it's basically readable.
609# This should also help prevent test bugs caused by writing tests
610# that simply parrot the original output generation code.
611
Richard Barnette5de01eb2017-12-15 09:53:42 -0800612_MODEL_MESSAGE_TEMPLATE = '''
613Model Avail Bad Idle Good Spare Total
xixuan12ce04f2016-03-10 13:16:30 -0800614lion -1 13 2 11 12 26
615tiger -1 5 2 9 4 16
616bear 0 5 2 10 5 17
617platypus 4 2 2 20 6 24
618aardvark 7 2 2 6 9 10
J. Richard Barnette96db3492015-03-27 17:23:52 -0700619'''
620
621
Richard Barnette5de01eb2017-12-15 09:53:42 -0800622class PoolSetInventoryTests(unittest.TestCase):
623 """Tests for `_generate_model_inventory_message()`.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700624
625 The tests create various test inventories designed to match the
Richard Barnette5de01eb2017-12-15 09:53:42 -0800626 counts in `_MODEL_MESSAGE_TEMPLATE`, and asserts that the
J. Richard Barnette96db3492015-03-27 17:23:52 -0700627 generated message text matches the original message text.
628
629 Message text is represented as a list of strings, split on the
630 `'\n'` separator.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700631 """
632
633 def setUp(self):
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800634 self.maxDiff = None
Richard Barnette5de01eb2017-12-15 09:53:42 -0800635 lines = [x.strip() for x in _MODEL_MESSAGE_TEMPLATE.split('\n') if
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800636 x.strip()]
Richard Barnette5de01eb2017-12-15 09:53:42 -0800637 self._header, self._model_lines = lines[0], lines[1:]
638 self._model_data = []
639 for l in self._model_lines:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700640 items = l.split()
Richard Barnette5de01eb2017-12-15 09:53:42 -0800641 model = items[0]
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800642 bad, idle, good, spare = [int(x) for x in items[2:-1]]
Richard Barnette5de01eb2017-12-15 09:53:42 -0800643 self._model_data.append((model, (good, bad, idle, spare)))
J. Richard Barnette96db3492015-03-27 17:23:52 -0700644
J. Richard Barnette96db3492015-03-27 17:23:52 -0700645 def _make_minimum_spares(self, counts):
646 """Create a counts tuple with as few spare DUTs as possible."""
xixuan12ce04f2016-03-10 13:16:30 -0800647 good, bad, idle, spares = counts
648 if spares > bad + idle:
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800649 return PoolStatusCounts(
650 StatusCounts(good + bad +idle - spares, 0, 0),
651 StatusCounts(spares - bad - idle, bad, idle),
652 )
xixuan12ce04f2016-03-10 13:16:30 -0800653 elif spares < bad:
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800654 return PoolStatusCounts(
655 StatusCounts(good, bad - spares, idle),
656 StatusCounts(0, spares, 0),
657 )
J. Richard Barnette96db3492015-03-27 17:23:52 -0700658 else:
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800659 return PoolStatusCounts(
660 StatusCounts(good, 0, idle + bad - spares),
661 StatusCounts(0, bad, spares - bad),
662 )
J. Richard Barnette96db3492015-03-27 17:23:52 -0700663
J. Richard Barnette96db3492015-03-27 17:23:52 -0700664 def _make_maximum_spares(self, counts):
665 """Create a counts tuple with as many spare DUTs as possible."""
xixuan12ce04f2016-03-10 13:16:30 -0800666 good, bad, idle, spares = counts
J. Richard Barnette96db3492015-03-27 17:23:52 -0700667 if good > spares:
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800668 return PoolStatusCounts(
669 StatusCounts(good - spares, bad, idle),
670 StatusCounts(spares, 0, 0),
671 )
xixuan12ce04f2016-03-10 13:16:30 -0800672 elif good + bad > spares:
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800673 return PoolStatusCounts(
674 StatusCounts(0, good + bad - spares, idle),
675 StatusCounts(good, spares - good, 0),
676 )
J. Richard Barnette96db3492015-03-27 17:23:52 -0700677 else:
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800678 return PoolStatusCounts(
679 StatusCounts(0, 0, good + bad + idle - spares),
680 StatusCounts(good, bad, spares - good - bad),
681 )
J. Richard Barnette96db3492015-03-27 17:23:52 -0700682
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800683 def _check_message(self, message):
684 """Checks that message approximately matches expected string."""
685 message = [x.strip() for x in message.split('\n') if x.strip()]
J. Richard Barnette96db3492015-03-27 17:23:52 -0700686 self.assertIn(self._header, message)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800687 body = message[message.index(self._header) + 1:]
Richard Barnette5de01eb2017-12-15 09:53:42 -0800688 self.assertEqual(body, self._model_lines)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700689
J. Richard Barnette96db3492015-03-27 17:23:52 -0700690 def test_minimum_spares(self):
691 """Test message generation when the spares pool is low."""
692 data = {
Richard Barnette5de01eb2017-12-15 09:53:42 -0800693 model: self._make_minimum_spares(counts)
694 for model, counts in self._model_data
J. Richard Barnette96db3492015-03-27 17:23:52 -0700695 }
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800696 inventory = create_inventory(data)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800697 message = lab_inventory._generate_model_inventory_message(inventory)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800698 self._check_message(message)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700699
700 def test_maximum_spares(self):
701 """Test message generation when the critical pool is low."""
702 data = {
Richard Barnette5de01eb2017-12-15 09:53:42 -0800703 model: self._make_maximum_spares(counts)
704 for model, counts in self._model_data
J. Richard Barnette96db3492015-03-27 17:23:52 -0700705 }
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800706 inventory = create_inventory(data)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800707 message = lab_inventory._generate_model_inventory_message(inventory)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800708 self._check_message(message)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700709
J. Richard Barnette96db3492015-03-27 17:23:52 -0700710 def test_ignore_no_spares(self):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800711 """Test that messages ignore models with no spare pool."""
J. Richard Barnette96db3492015-03-27 17:23:52 -0700712 data = {
Richard Barnette5de01eb2017-12-15 09:53:42 -0800713 model: self._make_maximum_spares(counts)
714 for model, counts in self._model_data
J. Richard Barnette96db3492015-03-27 17:23:52 -0700715 }
xixuan12ce04f2016-03-10 13:16:30 -0800716 data['elephant'] = ((5, 4, 0), (0, 0, 0))
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800717 inventory = create_inventory(data)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800718 message = lab_inventory._generate_model_inventory_message(inventory)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800719 self._check_message(message)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700720
J. Richard Barnette96db3492015-03-27 17:23:52 -0700721 def test_ignore_no_critical(self):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800722 """Test that messages ignore models with no critical pools."""
J. Richard Barnette96db3492015-03-27 17:23:52 -0700723 data = {
Richard Barnette5de01eb2017-12-15 09:53:42 -0800724 model: self._make_maximum_spares(counts)
725 for model, counts in self._model_data
J. Richard Barnette96db3492015-03-27 17:23:52 -0700726 }
xixuan12ce04f2016-03-10 13:16:30 -0800727 data['elephant'] = ((0, 0, 0), (1, 5, 1))
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800728 inventory = create_inventory(data)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800729 message = lab_inventory._generate_model_inventory_message(inventory)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800730 self._check_message(message)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700731
J. Richard Barnetteea5a4ba2016-02-18 16:34:50 -0800732 def test_ignore_no_bad(self):
Richard Barnette5de01eb2017-12-15 09:53:42 -0800733 """Test that messages ignore models with no bad DUTs."""
J. Richard Barnetteea5a4ba2016-02-18 16:34:50 -0800734 data = {
Richard Barnette5de01eb2017-12-15 09:53:42 -0800735 model: self._make_maximum_spares(counts)
736 for model, counts in self._model_data
J. Richard Barnetteea5a4ba2016-02-18 16:34:50 -0800737 }
xixuan12ce04f2016-03-10 13:16:30 -0800738 data['elephant'] = ((5, 0, 1), (5, 0, 1))
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800739 inventory = create_inventory(data)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800740 message = lab_inventory._generate_model_inventory_message(inventory)
Prathmesh Prabhu58aede82017-11-09 13:34:25 -0800741 self._check_message(message)
J. Richard Barnetteea5a4ba2016-02-18 16:34:50 -0800742
743
xixuan12ce04f2016-03-10 13:16:30 -0800744class _PoolInventoryTestBase(unittest.TestCase):
745 """Parent class for tests relating to generating pool inventory messages.
746
747 Func `setUp` in the class parses a given |message_template| to obtain
748 header and body.
749 """
Richard Barnettedf01f1b2018-04-20 14:44:40 -0400750
xixuan12ce04f2016-03-10 13:16:30 -0800751 def _read_template(self, message_template):
752 """Read message template for PoolInventoryTest and IdleInventoryTest.
753
754 @param message_template: the input template to be parsed into: header
755 and content (report_lines).
xixuan12ce04f2016-03-10 13:16:30 -0800756 """
757 message_lines = message_template.split('\n')
758 self._header = message_lines[1]
759 self._report_lines = message_lines[2:-1]
760
xixuan12ce04f2016-03-10 13:16:30 -0800761 def _check_report_no_info(self, text):
762 """Test a message body containing no reported info.
763
Richard Barnettedf01f1b2018-04-20 14:44:40 -0400764 The input `text` was created from a query to an inventory, which
765 has no objects meet the query and leads to an `empty` return.
766 Assert that the text consists of a single line starting with '('
767 and ending with ')'.
xixuan12ce04f2016-03-10 13:16:30 -0800768
769 @param text: Message body text to be tested.
xixuan12ce04f2016-03-10 13:16:30 -0800770 """
771 self.assertTrue(len(text) == 1 and
772 text[0][0] == '(' and
773 text[0][-1] == ')')
774
xixuan12ce04f2016-03-10 13:16:30 -0800775 def _check_report(self, text):
776 """Test a message against the passed |expected_content|.
777
778 @param text: Message body text to be tested.
779 @param expected_content: The ground-truth content to be compared with.
xixuan12ce04f2016-03-10 13:16:30 -0800780 """
781 self.assertEqual(text, self._report_lines)
782
783
J. Richard Barnette96db3492015-03-27 17:23:52 -0700784# _POOL_MESSAGE_TEMPLATE -
785# This is a sample of the output text produced by
786# _generate_pool_inventory_message(). This string is parsed by the
787# tests below to construct a sample inventory that should produce
788# the output, and then the output is generated and checked against
789# this original sample.
790#
791# See the comments on _BOARD_MESSAGE_TEMPLATE above for the
792# rationale on using sample text in this way.
793
794_POOL_MESSAGE_TEMPLATE = '''
Richard Barnette5de01eb2017-12-15 09:53:42 -0800795Model Bad Idle Good Total
xixuan12ce04f2016-03-10 13:16:30 -0800796lion 5 2 6 13
797tiger 4 1 5 10
798bear 3 0 7 10
799aardvark 2 0 0 2
800platypus 1 1 1 3
J. Richard Barnette96db3492015-03-27 17:23:52 -0700801'''
802
J. Richard Barnette4845fcf2015-04-20 14:26:25 -0700803_POOL_ADMIN_URL = 'http://go/cros-manage-duts'
804
805
xixuan12ce04f2016-03-10 13:16:30 -0800806class PoolInventoryTests(_PoolInventoryTestBase):
J. Richard Barnette96db3492015-03-27 17:23:52 -0700807 """Tests for `_generate_pool_inventory_message()`.
808
809 The tests create various test inventories designed to match the
810 counts in `_POOL_MESSAGE_TEMPLATE`, and assert that the
811 generated message text matches the format established in the
812 original message text.
813
814 The output message text is parsed against the following grammar:
J. Richard Barnette4845fcf2015-04-20 14:26:25 -0700815 <message> -> <intro> <pool> { "blank line" <pool> }
816 <intro> ->
817 Instructions to depty mentioning the admin page URL
818 A blank line
J. Richard Barnette96db3492015-03-27 17:23:52 -0700819 <pool> ->
820 <description>
821 <header line>
822 <message body>
823 <description> ->
824 Any number of lines describing one pool
825 <header line> ->
826 The header line from `_POOL_MESSAGE_TEMPLATE`
827 <message body> ->
828 Any number of non-blank lines
829
830 After parsing messages into the parts described above, various
831 assertions are tested against the parsed output, including
832 that the message body matches the body from
833 `_POOL_MESSAGE_TEMPLATE`.
834
835 Parse message text is represented as a list of strings, split on
836 the `'\n'` separator.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700837 """
Richard Barnette5de01eb2017-12-15 09:53:42 -0800838
J. Richard Barnette96db3492015-03-27 17:23:52 -0700839 def setUp(self):
xixuan12ce04f2016-03-10 13:16:30 -0800840 super(PoolInventoryTests, self)._read_template(_POOL_MESSAGE_TEMPLATE)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800841 self._model_data = []
xixuan12ce04f2016-03-10 13:16:30 -0800842 for l in self._report_lines:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700843 items = l.split()
Richard Barnette5de01eb2017-12-15 09:53:42 -0800844 model = items[0]
J. Richard Barnette96db3492015-03-27 17:23:52 -0700845 bad = int(items[1])
xixuan12ce04f2016-03-10 13:16:30 -0800846 idle = int(items[2])
847 good = int(items[3])
Richard Barnette5de01eb2017-12-15 09:53:42 -0800848 self._model_data.append((model, (good, bad, idle)))
J. Richard Barnette96db3492015-03-27 17:23:52 -0700849
Richard Barnette5de01eb2017-12-15 09:53:42 -0800850 def _create_histories(self, pools, model_data):
J. Richard Barnette96db3492015-03-27 17:23:52 -0700851 """Return a list suitable to create a `_LabInventory` object.
852
853 Creates a list of `_FakeHostHistory` objects that can be
854 used to create a lab inventory. `pools` is a list of strings
Richard Barnette5de01eb2017-12-15 09:53:42 -0800855 naming pools, and `model_data` is a list of tuples of the
J. Richard Barnette96db3492015-03-27 17:23:52 -0700856 form
Richard Barnette5de01eb2017-12-15 09:53:42 -0800857 `(model, (goodcount, badcount))`
J. Richard Barnette96db3492015-03-27 17:23:52 -0700858 where
Richard Barnette5de01eb2017-12-15 09:53:42 -0800859 `model` is a model name.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700860 `goodcount` is the number of working DUTs in the pool.
861 `badcount` is the number of broken DUTs in the pool.
862
863 @param pools List of pools for which to create
864 histories.
Richard Barnette5de01eb2017-12-15 09:53:42 -0800865 @param model_data List of tuples containing models and DUT
J. Richard Barnette96db3492015-03-27 17:23:52 -0700866 counts.
867 @return A list of `_FakeHostHistory` objects that can be
868 used to create a `_LabInventory` object.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700869 """
870 histories = []
xixuan12ce04f2016-03-10 13:16:30 -0800871 status_choices = (_WORKING, _BROKEN, _UNUSED)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700872 for pool in pools:
Richard Barnette5de01eb2017-12-15 09:53:42 -0800873 for model, counts in model_data:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700874 for status, count in zip(status_choices, counts):
875 for x in range(0, count):
876 histories.append(
Richard Barnette5de01eb2017-12-15 09:53:42 -0800877 _FakeHostHistory(model, pool, status))
J. Richard Barnette96db3492015-03-27 17:23:52 -0700878 return histories
879
J. Richard Barnette96db3492015-03-27 17:23:52 -0700880 def _parse_pool_summaries(self, histories):
881 """Parse message output according to the grammar above.
882
883 Create a lab inventory from the given `histories`, and
884 generate the pool inventory message. Then parse the message
885 and return a dictionary mapping each pool to the message
886 body parsed after that pool.
887
888 Tests the following assertions:
889 * Each <description> contains a mention of exactly one
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700890 pool in the `CRITICAL_POOLS` list.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700891 * Each pool is mentioned in exactly one <description>.
892 Note that the grammar requires the header to appear once
893 for each pool, so the parsing implicitly asserts that the
894 output contains the header.
895
896 @param histories Input used to create the test
897 `_LabInventory` object.
Richard Barnette5de01eb2017-12-15 09:53:42 -0800898 @return A dictionary mapping model names to the output
899 (a list of lines) for the model.
J. Richard Barnette96db3492015-03-27 17:23:52 -0700900 """
Richard Barnette5de01eb2017-12-15 09:53:42 -0800901 inventory = lab_inventory._LabInventory(
902 histories, lab_inventory.MANAGED_POOLS)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700903 message = lab_inventory._generate_pool_inventory_message(
xixuan12ce04f2016-03-10 13:16:30 -0800904 inventory).split('\n')
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700905 poolset = set(lab_inventory.CRITICAL_POOLS)
J. Richard Barnette4845fcf2015-04-20 14:26:25 -0700906 seen_url = False
907 seen_intro = False
J. Richard Barnette96db3492015-03-27 17:23:52 -0700908 description = ''
Richard Barnette5de01eb2017-12-15 09:53:42 -0800909 model_text = {}
J. Richard Barnette96db3492015-03-27 17:23:52 -0700910 current_pool = None
911 for line in message:
J. Richard Barnette4845fcf2015-04-20 14:26:25 -0700912 if not seen_url:
913 if _POOL_ADMIN_URL in line:
914 seen_url = True
915 elif not seen_intro:
916 if not line:
917 seen_intro = True
918 elif current_pool is None:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700919 if line == self._header:
920 pools_mentioned = [p for p in poolset
921 if p in description]
922 self.assertEqual(len(pools_mentioned), 1)
923 current_pool = pools_mentioned[0]
924 description = ''
Richard Barnette5de01eb2017-12-15 09:53:42 -0800925 model_text[current_pool] = []
J. Richard Barnette96db3492015-03-27 17:23:52 -0700926 poolset.remove(current_pool)
927 else:
928 description += line
929 else:
930 if line:
Richard Barnette5de01eb2017-12-15 09:53:42 -0800931 model_text[current_pool].append(line)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700932 else:
933 current_pool = None
934 self.assertEqual(len(poolset), 0)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800935 return model_text
J. Richard Barnette96db3492015-03-27 17:23:52 -0700936
J. Richard Barnette96db3492015-03-27 17:23:52 -0700937 def test_no_shortages(self):
938 """Test correct output when no pools have shortages."""
Richard Barnette5de01eb2017-12-15 09:53:42 -0800939 model_text = self._parse_pool_summaries([])
940 for text in model_text.values():
xixuan12ce04f2016-03-10 13:16:30 -0800941 self._check_report_no_info(text)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700942
J. Richard Barnette96db3492015-03-27 17:23:52 -0700943 def test_one_pool_shortage(self):
944 """Test correct output when exactly one pool has a shortage."""
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700945 for pool in lab_inventory.CRITICAL_POOLS:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700946 histories = self._create_histories((pool,),
Richard Barnette5de01eb2017-12-15 09:53:42 -0800947 self._model_data)
948 model_text = self._parse_pool_summaries(histories)
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700949 for checkpool in lab_inventory.CRITICAL_POOLS:
Richard Barnette5de01eb2017-12-15 09:53:42 -0800950 text = model_text[checkpool]
J. Richard Barnette96db3492015-03-27 17:23:52 -0700951 if checkpool == pool:
xixuan12ce04f2016-03-10 13:16:30 -0800952 self._check_report(text)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700953 else:
xixuan12ce04f2016-03-10 13:16:30 -0800954 self._check_report_no_info(text)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700955
J. Richard Barnette96db3492015-03-27 17:23:52 -0700956 def test_all_pool_shortages(self):
957 """Test correct output when all pools have a shortage."""
958 histories = []
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700959 for pool in lab_inventory.CRITICAL_POOLS:
J. Richard Barnette96db3492015-03-27 17:23:52 -0700960 histories.extend(
961 self._create_histories((pool,),
Richard Barnette5de01eb2017-12-15 09:53:42 -0800962 self._model_data))
963 model_text = self._parse_pool_summaries(histories)
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700964 for pool in lab_inventory.CRITICAL_POOLS:
Richard Barnette5de01eb2017-12-15 09:53:42 -0800965 self._check_report(model_text[pool])
J. Richard Barnette96db3492015-03-27 17:23:52 -0700966
Richard Barnette5de01eb2017-12-15 09:53:42 -0800967 def test_full_model_ignored(self):
968 """Test that models at full strength are not reported."""
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700969 pool = lab_inventory.CRITICAL_POOLS[0]
Richard Barnette5de01eb2017-12-15 09:53:42 -0800970 full_model = [('echidna', (5, 0, 0))]
J. Richard Barnette96db3492015-03-27 17:23:52 -0700971 histories = self._create_histories((pool,),
Richard Barnette5de01eb2017-12-15 09:53:42 -0800972 full_model)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700973 text = self._parse_pool_summaries(histories)[pool]
xixuan12ce04f2016-03-10 13:16:30 -0800974 self._check_report_no_info(text)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800975 model_data = self._model_data + full_model
976 histories = self._create_histories((pool,), model_data)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700977 text = self._parse_pool_summaries(histories)[pool]
xixuan12ce04f2016-03-10 13:16:30 -0800978 self._check_report(text)
J. Richard Barnette96db3492015-03-27 17:23:52 -0700979
J. Richard Barnette96db3492015-03-27 17:23:52 -0700980 def test_spare_pool_ignored(self):
981 """Test that reporting ignores the spare pool inventory."""
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700982 spare_pool = lab_inventory.SPARE_POOL
Richard Barnette5de01eb2017-12-15 09:53:42 -0800983 spare_data = self._model_data + [('echidna', (0, 5, 0))]
J. Richard Barnette96db3492015-03-27 17:23:52 -0700984 histories = self._create_histories((spare_pool,),
985 spare_data)
Richard Barnette5de01eb2017-12-15 09:53:42 -0800986 model_text = self._parse_pool_summaries(histories)
Kevin Chengcf0ad2b2016-04-19 14:51:39 -0700987 for pool in lab_inventory.CRITICAL_POOLS:
Richard Barnette5de01eb2017-12-15 09:53:42 -0800988 self._check_report_no_info(model_text[pool])
xixuan12ce04f2016-03-10 13:16:30 -0800989
990
991_IDLE_MESSAGE_TEMPLATE = '''
Richard Barnette5de01eb2017-12-15 09:53:42 -0800992Hostname Model Pool
xixuan12ce04f2016-03-10 13:16:30 -0800993chromeos4-row12-rack4-host7 tiger bvt
994chromeos1-row3-rack1-host2 lion bvt
995chromeos3-row2-rack2-host5 lion cq
996chromeos2-row7-rack3-host11 platypus suites
997'''
998
999
1000class IdleInventoryTests(_PoolInventoryTestBase):
1001 """Tests for `_generate_idle_inventory_message()`.
1002
1003 The tests create idle duts that match the counts and pool in
1004 `_IDLE_MESSAGE_TEMPLATE`. In test, it asserts that the generated
1005 idle message text matches the format established in
1006 `_IDLE_MESSAGE_TEMPLATE`.
1007
1008 Parse message text is represented as a list of strings, split on
1009 the `'\n'` separator.
xixuan12ce04f2016-03-10 13:16:30 -08001010 """
1011
1012 def setUp(self):
1013 super(IdleInventoryTests, self)._read_template(_IDLE_MESSAGE_TEMPLATE)
1014 self._host_data = []
1015 for h in self._report_lines:
1016 items = h.split()
1017 hostname = items[0]
Richard Barnette5de01eb2017-12-15 09:53:42 -08001018 model = items[1]
xixuan12ce04f2016-03-10 13:16:30 -08001019 pool = items[2]
Richard Barnette5de01eb2017-12-15 09:53:42 -08001020 self._host_data.append((hostname, model, pool))
xixuan12ce04f2016-03-10 13:16:30 -08001021 self._histories = []
Richard Barnette5de01eb2017-12-15 09:53:42 -08001022 self._histories.append(_FakeHostHistory('echidna', 'bvt', _BROKEN))
1023 self._histories.append(_FakeHostHistory('lion', 'bvt', _WORKING))
xixuan12ce04f2016-03-10 13:16:30 -08001024
xixuan12ce04f2016-03-10 13:16:30 -08001025 def _add_idles(self):
1026 """Add idle duts from `_IDLE_MESSAGE_TEMPLATE`."""
1027 idle_histories = [_FakeHostHistory(
Richard Barnette5de01eb2017-12-15 09:53:42 -08001028 model, pool, _UNUSED, hostname)
1029 for hostname, model, pool in self._host_data]
xixuan12ce04f2016-03-10 13:16:30 -08001030 self._histories.extend(idle_histories)
1031
xixuan12ce04f2016-03-10 13:16:30 -08001032 def _check_header(self, text):
1033 """Check whether header in the template `_IDLE_MESSAGE_TEMPLATE` is in
1034 passed text."""
1035 self.assertIn(self._header, text)
1036
xixuan12ce04f2016-03-10 13:16:30 -08001037 def _get_idle_message(self, histories):
1038 """Generate idle inventory and obtain its message.
1039
1040 @param histories: Used to create lab inventory.
1041
1042 @return the generated idle message.
xixuan12ce04f2016-03-10 13:16:30 -08001043 """
Richard Barnette5de01eb2017-12-15 09:53:42 -08001044 inventory = lab_inventory._LabInventory(
1045 histories, lab_inventory.MANAGED_POOLS)
xixuan12ce04f2016-03-10 13:16:30 -08001046 message = lab_inventory._generate_idle_inventory_message(
1047 inventory).split('\n')
1048 return message
1049
xixuan12ce04f2016-03-10 13:16:30 -08001050 def test_check_idle_inventory(self):
1051 """Test that reporting all the idle DUTs for every pool, sorted by
Kevin Chengcf0ad2b2016-04-19 14:51:39 -07001052 lab_inventory.MANAGED_POOLS.
xixuan12ce04f2016-03-10 13:16:30 -08001053 """
1054 self._add_idles()
1055
1056 message = self._get_idle_message(self._histories)
1057 self._check_header(message)
1058 self._check_report(message[message.index(self._header) + 1 :])
1059
xixuan12ce04f2016-03-10 13:16:30 -08001060 def test_no_idle_inventory(self):
1061 """Test that reporting no idle DUTs."""
1062 message = self._get_idle_message(self._histories)
1063 self._check_header(message)
1064 self._check_report_no_info(
1065 message[message.index(self._header) + 1 :])
J. Richard Barnette96db3492015-03-27 17:23:52 -07001066
1067
1068class CommandParsingTests(unittest.TestCase):
1069 """Tests for command line argument parsing in `_parse_command()`."""
1070
Richard Barnette7bfcb032018-02-26 11:46:56 -08001071 # At least one of these options must be specified on every command
1072 # line; otherwise, the command line parsing will fail.
Richard Barnette1ca30e62018-04-09 16:45:58 -07001073 _REPORT_OPTIONS = [
1074 '--model-notify=', '--pool-notify=', '--report-untestable'
1075 ]
J. Richard Barnette02e82432015-10-13 16:02:47 -07001076
J. Richard Barnette96db3492015-03-27 17:23:52 -07001077 def setUp(self):
1078 dirpath = '/usr/local/fubar'
1079 self._command_path = os.path.join(dirpath,
1080 'site_utils',
1081 'arglebargle')
1082 self._logdir = os.path.join(dirpath, lab_inventory._LOGDIR)
1083
Richard Barnette7bfcb032018-02-26 11:46:56 -08001084 def _parse_arguments(self, argv):
1085 """Test parsing with explictly passed report options."""
1086 full_argv = [self._command_path] + argv
J. Richard Barnette96db3492015-03-27 17:23:52 -07001087 return lab_inventory._parse_command(full_argv)
1088
Richard Barnette7bfcb032018-02-26 11:46:56 -08001089 def _parse_non_report_arguments(self, argv):
1090 """Test parsing for non-report command-line options."""
1091 return self._parse_arguments(argv + self._REPORT_OPTIONS)
1092
Richard Barnette7bfcb032018-02-26 11:46:56 -08001093 def _check_non_report_defaults(self, report_option):
1094 arguments = self._parse_arguments([report_option])
J. Richard Barnette02e82432015-10-13 16:02:47 -07001095 self.assertEqual(arguments.duration,
1096 lab_inventory._DEFAULT_DURATION)
Richard Barnettecf5d8342017-10-24 18:13:11 -07001097 self.assertIsNone(arguments.recommend)
J. Richard Barnette02e82432015-10-13 16:02:47 -07001098 self.assertFalse(arguments.debug)
1099 self.assertEqual(arguments.logdir, self._logdir)
Richard Barnette5de01eb2017-12-15 09:53:42 -08001100 self.assertEqual(arguments.modelnames, [])
J. Richard Barnette02e82432015-10-13 16:02:47 -07001101 return arguments
1102
J. Richard Barnette02e82432015-10-13 16:02:47 -07001103 def test_empty_arguments(self):
Richard Barnette7bfcb032018-02-26 11:46:56 -08001104 """Test that no reports requested is an error."""
1105 arguments = self._parse_arguments([])
J. Richard Barnette02e82432015-10-13 16:02:47 -07001106 self.assertIsNone(arguments)
1107
J. Richard Barnette96db3492015-03-27 17:23:52 -07001108 def test_argument_defaults(self):
1109 """Test that option defaults match expectations."""
Richard Barnette7bfcb032018-02-26 11:46:56 -08001110 for report in self._REPORT_OPTIONS:
1111 arguments = self._check_non_report_defaults(report)
1112
Richard Barnette7bfcb032018-02-26 11:46:56 -08001113 def test_model_notify_defaults(self):
1114 """Test defaults when `--model-notify` is specified alone."""
1115 arguments = self._parse_arguments(['--model-notify='])
Richard Barnette5de01eb2017-12-15 09:53:42 -08001116 self.assertEqual(arguments.model_notify, [''])
J. Richard Barnette96db3492015-03-27 17:23:52 -07001117 self.assertEqual(arguments.pool_notify, [])
Richard Barnette1ca30e62018-04-09 16:45:58 -07001118 self.assertFalse(arguments.report_untestable)
Richard Barnette7bfcb032018-02-26 11:46:56 -08001119
Richard Barnette7bfcb032018-02-26 11:46:56 -08001120 def test_pool_notify_defaults(self):
1121 """Test defaults when `--pool-notify` is specified alone."""
1122 arguments = self._parse_arguments(['--pool-notify='])
Richard Barnette5de01eb2017-12-15 09:53:42 -08001123 self.assertEqual(arguments.model_notify, [])
J. Richard Barnette02e82432015-10-13 16:02:47 -07001124 self.assertEqual(arguments.pool_notify, [''])
Richard Barnette1ca30e62018-04-09 16:45:58 -07001125 self.assertFalse(arguments.report_untestable)
Richard Barnette7bfcb032018-02-26 11:46:56 -08001126
Richard Barnette1ca30e62018-04-09 16:45:58 -07001127 def test_report_untestable_defaults(self):
1128 """Test defaults when `--report-untestable` is specified alone."""
1129 arguments = self._parse_arguments(['--report-untestable'])
Richard Barnette7bfcb032018-02-26 11:46:56 -08001130 self.assertEqual(arguments.model_notify, [])
1131 self.assertEqual(arguments.pool_notify, [])
Richard Barnette1ca30e62018-04-09 16:45:58 -07001132 self.assertTrue(arguments.report_untestable)
J. Richard Barnette96db3492015-03-27 17:23:52 -07001133
Richard Barnette5de01eb2017-12-15 09:53:42 -08001134 def test_model_arguments(self):
1135 """Test that non-option arguments are returned in `modelnames`."""
1136 modellist = ['aardvark', 'echidna']
Richard Barnette7bfcb032018-02-26 11:46:56 -08001137 arguments = self._parse_non_report_arguments(modellist)
Richard Barnette5de01eb2017-12-15 09:53:42 -08001138 self.assertEqual(arguments.modelnames, modellist)
J. Richard Barnette96db3492015-03-27 17:23:52 -07001139
Richard Barnettecf5d8342017-10-24 18:13:11 -07001140 def test_recommend_option(self):
1141 """Test parsing of the `--recommend` option."""
1142 for opt in ['-r', '--recommend']:
1143 for recommend in ['5', '55']:
Richard Barnette7bfcb032018-02-26 11:46:56 -08001144 arguments = self._parse_non_report_arguments([opt, recommend])
Richard Barnettecf5d8342017-10-24 18:13:11 -07001145 self.assertEqual(arguments.recommend, int(recommend))
1146
J. Richard Barnette02e82432015-10-13 16:02:47 -07001147 def test_debug_option(self):
1148 """Test parsing of the `--debug` option."""
Richard Barnette7bfcb032018-02-26 11:46:56 -08001149 arguments = self._parse_non_report_arguments(['--debug'])
J. Richard Barnette02e82432015-10-13 16:02:47 -07001150 self.assertTrue(arguments.debug)
J. Richard Barnette96db3492015-03-27 17:23:52 -07001151
J. Richard Barnette96db3492015-03-27 17:23:52 -07001152 def test_duration(self):
1153 """Test parsing of the `--duration` option."""
Richard Barnettecf5d8342017-10-24 18:13:11 -07001154 for opt in ['-d', '--duration']:
1155 for duration in ['1', '11']:
Richard Barnette7bfcb032018-02-26 11:46:56 -08001156 arguments = self._parse_non_report_arguments([opt, duration])
Richard Barnettecf5d8342017-10-24 18:13:11 -07001157 self.assertEqual(arguments.duration, int(duration))
J. Richard Barnette96db3492015-03-27 17:23:52 -07001158
J. Richard Barnette96db3492015-03-27 17:23:52 -07001159 def _check_email_option(self, option, getlist):
1160 """Test parsing of e-mail address options.
1161
Richard Barnette5de01eb2017-12-15 09:53:42 -08001162 This is a helper function to test the `--model-notify` and
J. Richard Barnette96db3492015-03-27 17:23:52 -07001163 `--pool-notify` options. It tests the following cases:
1164 * `--option a1` gives the list [a1]
1165 * `--option ' a1 '` gives the list [a1]
1166 * `--option a1 --option a2` gives the list [a1, a2]
1167 * `--option a1,a2` gives the list [a1, a2]
1168 * `--option 'a1, a2'` gives the list [a1, a2]
1169
1170 @param option The option to be tested.
1171 @param getlist A function to return the option's value from
1172 parsed command line arguments.
J. Richard Barnette96db3492015-03-27 17:23:52 -07001173 """
1174 a1 = 'mumble@mumbler.com'
1175 a2 = 'bumble@bumbler.org'
Richard Barnette7bfcb032018-02-26 11:46:56 -08001176 arguments = self._parse_arguments([option, a1])
J. Richard Barnette96db3492015-03-27 17:23:52 -07001177 self.assertEqual(getlist(arguments), [a1])
Richard Barnette7bfcb032018-02-26 11:46:56 -08001178 arguments = self._parse_arguments([option, ' ' + a1 + ' '])
J. Richard Barnette96db3492015-03-27 17:23:52 -07001179 self.assertEqual(getlist(arguments), [a1])
Richard Barnette7bfcb032018-02-26 11:46:56 -08001180 arguments = self._parse_arguments([option, a1, option, a2])
J. Richard Barnette96db3492015-03-27 17:23:52 -07001181 self.assertEqual(getlist(arguments), [a1, a2])
1182 arguments = self._parse_arguments(
Richard Barnette7bfcb032018-02-26 11:46:56 -08001183 [option, ','.join([a1, a2])])
J. Richard Barnette96db3492015-03-27 17:23:52 -07001184 self.assertEqual(getlist(arguments), [a1, a2])
1185 arguments = self._parse_arguments(
Richard Barnette7bfcb032018-02-26 11:46:56 -08001186 [option, ', '.join([a1, a2])])
J. Richard Barnette96db3492015-03-27 17:23:52 -07001187 self.assertEqual(getlist(arguments), [a1, a2])
1188
Richard Barnette5de01eb2017-12-15 09:53:42 -08001189 def test_model_notify(self):
1190 """Test parsing of the `--model-notify` option."""
1191 self._check_email_option('--model-notify',
1192 lambda a: a.model_notify)
J. Richard Barnette96db3492015-03-27 17:23:52 -07001193
J. Richard Barnette96db3492015-03-27 17:23:52 -07001194 def test_pool_notify(self):
1195 """Test parsing of the `--pool-notify` option."""
1196 self._check_email_option('--pool-notify',
1197 lambda a: a.pool_notify)
1198
Richard Barnettecf5d8342017-10-24 18:13:11 -07001199 def test_logdir_option(self):
J. Richard Barnette96db3492015-03-27 17:23:52 -07001200 """Test parsing of the `--logdir` option."""
1201 logdir = '/usr/local/whatsis/logs'
Richard Barnette7bfcb032018-02-26 11:46:56 -08001202 arguments = self._parse_non_report_arguments(['--logdir', logdir])
J. Richard Barnette96db3492015-03-27 17:23:52 -07001203 self.assertEqual(arguments.logdir, logdir)
1204
1205
1206if __name__ == '__main__':
J. Richard Barnettef60a1ee2015-06-02 10:52:37 -07001207 # Some of the functions we test log messages. Prevent those
1208 # messages from showing up in test output.
1209 logging.getLogger().setLevel(logging.CRITICAL)
J. Richard Barnette96db3492015-03-27 17:23:52 -07001210 unittest.main()