You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1211 lines
47 KiB
1211 lines
47 KiB
#!/usr/bin/env python2
|
|
# Copyright 2015 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import collections
|
|
import itertools
|
|
import logging
|
|
import os
|
|
import unittest
|
|
|
|
import common
|
|
from autotest_lib.frontend.afe.json_rpc import proxy
|
|
from autotest_lib.server.lib import status_history
|
|
from autotest_lib.site_utils import lab_inventory
|
|
|
|
|
|
# _FAKE_TIME - an arbitrary but plausible time_t value.
|
|
# You can make your own with `date +%s`.
|
|
|
|
_FAKE_TIME = 1537457599
|
|
|
|
|
|
class _FakeHost(object):
|
|
"""Class to mock `Host` in _FakeHostHistory for testing."""
|
|
|
|
def __init__(self, hostname):
|
|
self.hostname = hostname
|
|
|
|
|
|
class _FakeHostEvent(object):
|
|
def __init__(self, time):
|
|
self.start_time = time
|
|
self.end_time = time + 1
|
|
|
|
|
|
class _FakeHostHistory(object):
|
|
"""Class to mock `HostJobHistory` for testing."""
|
|
|
|
def __init__(self, model, pool, status, hostname=''):
|
|
self.host_model = model
|
|
self.host_board = model + '_board'
|
|
self.host_pool = pool
|
|
self.status = status
|
|
self.host = _FakeHost(hostname)
|
|
self.hostname = hostname
|
|
self.start_time = _FAKE_TIME
|
|
self.end_time = _FAKE_TIME + 20
|
|
self.fake_task = _FakeHostEvent(_FAKE_TIME + 5)
|
|
self.exception = None
|
|
|
|
def last_diagnosis(self):
|
|
"""Return the recorded diagnosis."""
|
|
if self.exception:
|
|
raise self.exception
|
|
else:
|
|
return self.status, self.fake_task
|
|
|
|
|
|
class _FakeHostLocation(object):
|
|
"""Class to mock `HostJobHistory` for location sorting."""
|
|
|
|
_HOSTNAME_FORMAT = 'chromeos%d-row%d-rack%d-host%d'
|
|
|
|
def __init__(self, location):
|
|
self.hostname = self._HOSTNAME_FORMAT % location
|
|
|
|
@property
|
|
def host(self):
|
|
"""Return a fake host object with a hostname."""
|
|
return self
|
|
|
|
|
|
# Status values that may be returned by `HostJobHistory`.
|
|
#
|
|
# These merely rename the corresponding values in `status_history`
|
|
# for convenience.
|
|
|
|
_WORKING = status_history.WORKING
|
|
_UNUSED = status_history.UNUSED
|
|
_BROKEN = status_history.BROKEN
|
|
_UNKNOWN = status_history.UNKNOWN
|
|
|
|
|
|
class GetStatusTestCase(unittest.TestCase):
|
|
"""Tests for `_get_diagnosis()`."""
|
|
|
|
def _get_diagnosis_status(self, history):
|
|
return lab_inventory._get_diagnosis(history).status
|
|
|
|
def test_working_and_in_range(self):
|
|
"""Test WORKING when task times are in the history range."""
|
|
history = _FakeHostHistory('', '', _WORKING)
|
|
history.fake_task = _FakeHostEvent(history.start_time + 1)
|
|
self.assertEqual(self._get_diagnosis_status(history), _WORKING)
|
|
|
|
def test_broken_and_in_range(self):
|
|
"""Test BROKEN when task times are in the history range."""
|
|
history = _FakeHostHistory('', '', _BROKEN)
|
|
history.fake_task = _FakeHostEvent(history.start_time + 1)
|
|
self.assertEqual(self._get_diagnosis_status(history), _BROKEN)
|
|
|
|
def test_broken_and_straddles(self):
|
|
"""Test BROKEN when task time straddles the history start point."""
|
|
history = _FakeHostHistory('', '', _BROKEN)
|
|
history.fake_task = _FakeHostEvent(history.start_time - 1)
|
|
self.assertEqual(self._get_diagnosis_status(history), _BROKEN)
|
|
|
|
def test_broken_and_out_of_range(self):
|
|
"""Test BROKEN when task times are before the history range."""
|
|
history = _FakeHostHistory('', '', _BROKEN)
|
|
history.fake_task = _FakeHostEvent(history.start_time - 2)
|
|
self.assertEqual(self._get_diagnosis_status(history), _UNUSED)
|
|
|
|
def test_exception(self):
|
|
"""Test exceptions raised by `last_diagnosis()`."""
|
|
history = _FakeHostHistory('', '', _BROKEN)
|
|
history.exception = proxy.JSONRPCException('exception for testing')
|
|
self.assertIsNone(self._get_diagnosis_status(history))
|
|
|
|
|
|
class HostSetInventoryTestCase(unittest.TestCase):
|
|
"""Unit tests for class `_HostSetInventory`.
|
|
|
|
Coverage is quite basic: mostly just enough to make sure every
|
|
function gets called, and to make sure that the counting knows
|
|
the difference between 0 and 1.
|
|
|
|
The testing also ensures that all known status values that can be
|
|
returned by `HostJobHistory` are counted as expected.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(HostSetInventoryTestCase, self).setUp()
|
|
self.histories = lab_inventory._HostSetInventory()
|
|
|
|
def _add_host(self, status):
|
|
fake = _FakeHostHistory('zebra', lab_inventory.SPARE_POOL, status)
|
|
self.histories.record_host(fake)
|
|
|
|
def _check_counts(self, working, broken, idle):
|
|
"""Check that pool counts match expectations.
|
|
|
|
Asserts that `get_working()`, `get_broken()`, and `get_idle()`
|
|
return the given expected values. Also assert that
|
|
`get_total()` is the sum of all counts.
|
|
|
|
@param working The expected total of working devices.
|
|
@param broken The expected total of broken devices.
|
|
@param idle The expected total of idle devices.
|
|
"""
|
|
self.assertEqual(self.histories.get_working(), working)
|
|
self.assertEqual(self.histories.get_broken(), broken)
|
|
self.assertEqual(self.histories.get_idle(), idle)
|
|
self.assertEqual(self.histories.get_total(),
|
|
working + broken + idle)
|
|
|
|
def test_empty(self):
|
|
"""Test counts when there are no DUTs recorded."""
|
|
self._check_counts(0, 0, 0)
|
|
|
|
def test_broken(self):
|
|
"""Test counting for broken DUTs."""
|
|
self._add_host(_BROKEN)
|
|
self._check_counts(0, 1, 0)
|
|
|
|
def test_working(self):
|
|
"""Test counting for working DUTs."""
|
|
self._add_host(_WORKING)
|
|
self._check_counts(1, 0, 0)
|
|
|
|
def test_idle(self):
|
|
"""Testing counting for idle status values."""
|
|
self._add_host(_UNUSED)
|
|
self._check_counts(0, 0, 1)
|
|
self._add_host(_UNKNOWN)
|
|
self._check_counts(0, 0, 2)
|
|
|
|
def test_working_then_broken(self):
|
|
"""Test counts after adding a working and then a broken DUT."""
|
|
self._add_host(_WORKING)
|
|
self._add_host(_BROKEN)
|
|
self._check_counts(1, 1, 0)
|
|
|
|
def test_broken_then_working(self):
|
|
"""Test counts after adding a broken and then a working DUT."""
|
|
self._add_host(_BROKEN)
|
|
self._add_host(_WORKING)
|
|
self._check_counts(1, 1, 0)
|
|
|
|
|
|
class PoolSetInventoryTestCase(unittest.TestCase):
|
|
"""Unit tests for class `_PoolSetInventory`.
|
|
|
|
Coverage is quite basic: just enough to make sure every function
|
|
gets called, and to make sure that the counting knows the difference
|
|
between 0 and 1.
|
|
|
|
The tests make sure that both individual pool counts and totals are
|
|
counted correctly.
|
|
"""
|
|
|
|
_POOL_SET = ['humpty', 'dumpty']
|
|
|
|
def setUp(self):
|
|
super(PoolSetInventoryTestCase, self).setUp()
|
|
self._pool_histories = lab_inventory._PoolSetInventory(self._POOL_SET)
|
|
|
|
def _add_host(self, pool, status):
|
|
fake = _FakeHostHistory('zebra', pool, status)
|
|
self._pool_histories.record_host(fake)
|
|
|
|
def _check_all_counts(self, working, broken):
|
|
"""Check that total counts for all pools match expectations.
|
|
|
|
Checks that `get_working()` and `get_broken()` return the
|
|
given expected values when called without a pool specified.
|
|
Also check that `get_total()` is the sum of working and
|
|
broken devices.
|
|
|
|
Additionally, call the various functions for all the pools
|
|
individually, and confirm that the totals across pools match
|
|
the given expectations.
|
|
|
|
@param working The expected total of working devices.
|
|
@param broken The expected total of broken devices.
|
|
"""
|
|
self.assertEqual(self._pool_histories.get_working(), working)
|
|
self.assertEqual(self._pool_histories.get_broken(), broken)
|
|
self.assertEqual(self._pool_histories.get_total(),
|
|
working + broken)
|
|
count_working = 0
|
|
count_broken = 0
|
|
count_total = 0
|
|
for pool in self._POOL_SET:
|
|
count_working += self._pool_histories.get_working(pool)
|
|
count_broken += self._pool_histories.get_broken(pool)
|
|
count_total += self._pool_histories.get_total(pool)
|
|
self.assertEqual(count_working, working)
|
|
self.assertEqual(count_broken, broken)
|
|
self.assertEqual(count_total, working + broken)
|
|
|
|
def _check_pool_counts(self, pool, working, broken):
|
|
"""Check that counts for a given pool match expectations.
|
|
|
|
Checks that `get_working()` and `get_broken()` return the
|
|
given expected values for the given pool. Also check that
|
|
`get_total()` is the sum of working and broken devices.
|
|
|
|
@param pool The pool to be checked.
|
|
@param working The expected total of working devices.
|
|
@param broken The expected total of broken devices.
|
|
"""
|
|
self.assertEqual(self._pool_histories.get_working(pool),
|
|
working)
|
|
self.assertEqual(self._pool_histories.get_broken(pool),
|
|
broken)
|
|
self.assertEqual(self._pool_histories.get_total(pool),
|
|
working + broken)
|
|
|
|
def test_empty(self):
|
|
"""Test counts when there are no DUTs recorded."""
|
|
self._check_all_counts(0, 0)
|
|
for pool in self._POOL_SET:
|
|
self._check_pool_counts(pool, 0, 0)
|
|
|
|
def test_all_working_then_broken(self):
|
|
"""Test counts after adding a working and then a broken DUT.
|
|
|
|
For each pool, add first a working, then a broken DUT. After
|
|
each DUT is added, check counts to confirm the correct values.
|
|
"""
|
|
working = 0
|
|
broken = 0
|
|
for pool in self._POOL_SET:
|
|
self._add_host(pool, _WORKING)
|
|
working += 1
|
|
self._check_pool_counts(pool, 1, 0)
|
|
self._check_all_counts(working, broken)
|
|
self._add_host(pool, _BROKEN)
|
|
broken += 1
|
|
self._check_pool_counts(pool, 1, 1)
|
|
self._check_all_counts(working, broken)
|
|
|
|
def test_all_broken_then_working(self):
|
|
"""Test counts after adding a broken and then a working DUT.
|
|
|
|
For each pool, add first a broken, then a working DUT. After
|
|
each DUT is added, check counts to confirm the correct values.
|
|
"""
|
|
working = 0
|
|
broken = 0
|
|
for pool in self._POOL_SET:
|
|
self._add_host(pool, _BROKEN)
|
|
broken += 1
|
|
self._check_pool_counts(pool, 0, 1)
|
|
self._check_all_counts(working, broken)
|
|
self._add_host(pool, _WORKING)
|
|
working += 1
|
|
self._check_pool_counts(pool, 1, 1)
|
|
self._check_all_counts(working, broken)
|
|
|
|
|
|
class LocationSortTests(unittest.TestCase):
|
|
"""Unit tests for `_sort_by_location()`."""
|
|
|
|
def setUp(self):
|
|
super(LocationSortTests, self).setUp()
|
|
|
|
def _check_sorting(self, *locations):
|
|
"""Test sorting a given list of locations.
|
|
|
|
The input is an already ordered list of lists of tuples with
|
|
row, rack, and host numbers. The test converts the tuples
|
|
to hostnames, preserving the original ordering. Then it
|
|
flattens and scrambles the input, runs it through
|
|
`_sort_by_location()`, and asserts that the result matches
|
|
the original.
|
|
"""
|
|
lab = 0
|
|
expected = []
|
|
for tuples in locations:
|
|
lab += 1
|
|
expected.append(
|
|
[_FakeHostLocation((lab,) + t) for t in tuples])
|
|
scrambled = [e for e in itertools.chain(*expected)]
|
|
scrambled = [e for e in reversed(scrambled)]
|
|
actual = lab_inventory._sort_by_location(scrambled)
|
|
# The ordering of the labs in the output isn't guaranteed,
|
|
# so we can't compare `expected` and `actual` directly.
|
|
# Instead, we create a dictionary keyed on the first host in
|
|
# each lab, and compare the dictionaries.
|
|
self.assertEqual({l[0]: l for l in expected},
|
|
{l[0]: l for l in actual})
|
|
|
|
def test_separate_labs(self):
|
|
"""Test that sorting distinguishes labs."""
|
|
self._check_sorting([(1, 1, 1)], [(1, 1, 1)], [(1, 1, 1)])
|
|
|
|
def test_separate_rows(self):
|
|
"""Test for proper sorting when only rows are different."""
|
|
self._check_sorting([(1, 1, 1), (9, 1, 1), (10, 1, 1)])
|
|
|
|
def test_separate_racks(self):
|
|
"""Test for proper sorting when only racks are different."""
|
|
self._check_sorting([(1, 1, 1), (1, 9, 1), (1, 10, 1)])
|
|
|
|
def test_separate_hosts(self):
|
|
"""Test for proper sorting when only hosts are different."""
|
|
self._check_sorting([(1, 1, 1), (1, 1, 9), (1, 1, 10)])
|
|
|
|
def test_diagonal(self):
|
|
"""Test for proper sorting when all parts are different."""
|
|
self._check_sorting([(1, 1, 2), (1, 2, 1), (2, 1, 1)])
|
|
|
|
|
|
class InventoryScoringTests(unittest.TestCase):
|
|
"""Unit tests for `_score_repair_set()`."""
|
|
|
|
def setUp(self):
|
|
super(InventoryScoringTests, self).setUp()
|
|
|
|
def _make_buffer_counts(self, *counts):
|
|
"""Create a dictionary suitable as `buffer_counts`.
|
|
|
|
@param counts List of tuples with model count data.
|
|
"""
|
|
self._buffer_counts = dict(counts)
|
|
|
|
def _make_history_list(self, repair_counts):
|
|
"""Create a list suitable as `repair_list`.
|
|
|
|
@param repair_counts List of (model, count) tuples.
|
|
"""
|
|
pool = lab_inventory.SPARE_POOL
|
|
histories = []
|
|
for model, count in repair_counts:
|
|
for i in range(0, count):
|
|
histories.append(
|
|
_FakeHostHistory(model, pool, _BROKEN))
|
|
return histories
|
|
|
|
def _check_better(self, repair_a, repair_b):
|
|
"""Test that repair set A scores better than B.
|
|
|
|
Contruct repair sets from `repair_a` and `repair_b`,
|
|
and score both of them using the pre-existing
|
|
`self._buffer_counts`. Assert that the score for A is
|
|
better than the score for B.
|
|
|
|
@param repair_a Input data for repair set A
|
|
@param repair_b Input data for repair set B
|
|
"""
|
|
score_a = lab_inventory._score_repair_set(
|
|
self._buffer_counts,
|
|
self._make_history_list(repair_a))
|
|
score_b = lab_inventory._score_repair_set(
|
|
self._buffer_counts,
|
|
self._make_history_list(repair_b))
|
|
self.assertGreater(score_a, score_b)
|
|
|
|
def _check_equal(self, repair_a, repair_b):
|
|
"""Test that repair set A scores the same as B.
|
|
|
|
Contruct repair sets from `repair_a` and `repair_b`,
|
|
and score both of them using the pre-existing
|
|
`self._buffer_counts`. Assert that the score for A is
|
|
equal to the score for B.
|
|
|
|
@param repair_a Input data for repair set A
|
|
@param repair_b Input data for repair set B
|
|
"""
|
|
score_a = lab_inventory._score_repair_set(
|
|
self._buffer_counts,
|
|
self._make_history_list(repair_a))
|
|
score_b = lab_inventory._score_repair_set(
|
|
self._buffer_counts,
|
|
self._make_history_list(repair_b))
|
|
self.assertEqual(score_a, score_b)
|
|
|
|
def test_improve_worst_model(self):
|
|
"""Test that improving the worst model improves scoring.
|
|
|
|
Construct a buffer counts dictionary with all models having
|
|
different counts. Assert that it is both necessary and
|
|
sufficient to improve the count of the worst model in order
|
|
to improve the score.
|
|
"""
|
|
self._make_buffer_counts(('lion', 0),
|
|
('tiger', 1),
|
|
('bear', 2))
|
|
self._check_better([('lion', 1)], [('tiger', 1)])
|
|
self._check_better([('lion', 1)], [('bear', 1)])
|
|
self._check_better([('lion', 1)], [('tiger', 2)])
|
|
self._check_better([('lion', 1)], [('bear', 2)])
|
|
self._check_equal([('tiger', 1)], [('bear', 1)])
|
|
|
|
def test_improve_worst_case_count(self):
|
|
"""Test that improving the number of worst cases improves the score.
|
|
|
|
Construct a buffer counts dictionary with all models having
|
|
the same counts. Assert that improving two models is better
|
|
than improving one. Assert that improving any one model is
|
|
as good as any other.
|
|
"""
|
|
self._make_buffer_counts(('lion', 0),
|
|
('tiger', 0),
|
|
('bear', 0))
|
|
self._check_better([('lion', 1), ('tiger', 1)], [('bear', 2)])
|
|
self._check_equal([('lion', 2)], [('tiger', 1)])
|
|
self._check_equal([('tiger', 1)], [('bear', 1)])
|
|
|
|
|
|
# Each item is the number of DUTs in that status.
|
|
STATUS_CHOICES = (_WORKING, _BROKEN, _UNUSED)
|
|
StatusCounts = collections.namedtuple('StatusCounts', ['good', 'bad', 'idle'])
|
|
# Each item is a StatusCounts tuple specifying the number of DUTs per status in
|
|
# the that pool.
|
|
CRITICAL_POOL = lab_inventory.CRITICAL_POOLS[0]
|
|
SPARE_POOL = lab_inventory.SPARE_POOL
|
|
POOL_CHOICES = (CRITICAL_POOL, SPARE_POOL)
|
|
PoolStatusCounts = collections.namedtuple('PoolStatusCounts',
|
|
['critical', 'spare'])
|
|
|
|
def create_inventory(data):
|
|
"""Create a `_LabInventory` instance for testing.
|
|
|
|
This function allows the construction of a complete `_LabInventory`
|
|
object from a simplified input representation.
|
|
|
|
A single 'critical pool' is arbitrarily chosen for purposes of
|
|
testing; there's no coverage for testing arbitrary combinations
|
|
in more than one critical pool.
|
|
|
|
@param data: dict {key: PoolStatusCounts}.
|
|
@returns: lab_inventory._LabInventory object.
|
|
"""
|
|
histories = []
|
|
for model, counts in data.iteritems():
|
|
for p, pool in enumerate(POOL_CHOICES):
|
|
for s, status in enumerate(STATUS_CHOICES):
|
|
fake_host = _FakeHostHistory(model, pool, status)
|
|
histories.extend([fake_host] * counts[p][s])
|
|
inventory = lab_inventory._LabInventory(
|
|
histories, lab_inventory.MANAGED_POOLS)
|
|
return inventory
|
|
|
|
|
|
class LabInventoryTests(unittest.TestCase):
|
|
"""Tests for the basic functions of `_LabInventory`.
|
|
|
|
Contains basic coverage to show that after an inventory is created
|
|
and DUTs with known status are added, the inventory counts match the
|
|
counts of the added DUTs.
|
|
"""
|
|
|
|
_MODEL_LIST = ['lion', 'tiger', 'bear'] # Oh, my!
|
|
|
|
def _check_inventory_counts(self, inventory, data, msg=None):
|
|
"""Check that all counts in the inventory match `data`.
|
|
|
|
This asserts that the actual counts returned by the various
|
|
accessor functions for `inventory` match the values expected for
|
|
the given `data` that created the inventory.
|
|
|
|
@param inventory: _LabInventory object to check.
|
|
@param data Inventory data to check against. Same type as
|
|
`create_inventory`.
|
|
"""
|
|
self.assertEqual(set(inventory.keys()), set(data.keys()))
|
|
for model, histories in inventory.iteritems():
|
|
expected_counts = data[model]
|
|
actual_counts = PoolStatusCounts(
|
|
StatusCounts(
|
|
histories.get_working(CRITICAL_POOL),
|
|
histories.get_broken(CRITICAL_POOL),
|
|
histories.get_idle(CRITICAL_POOL),
|
|
),
|
|
StatusCounts(
|
|
histories.get_working(SPARE_POOL),
|
|
histories.get_broken(SPARE_POOL),
|
|
histories.get_idle(SPARE_POOL),
|
|
),
|
|
)
|
|
self.assertEqual(actual_counts, expected_counts, msg)
|
|
|
|
self.assertEqual(len(histories.get_working_list()),
|
|
sum([p.good for p in expected_counts]),
|
|
msg)
|
|
self.assertEqual(len(histories.get_broken_list()),
|
|
sum([p.bad for p in expected_counts]),
|
|
msg)
|
|
self.assertEqual(len(histories.get_idle_list()),
|
|
sum([p.idle for p in expected_counts]),
|
|
msg)
|
|
|
|
def test_empty(self):
|
|
"""Test counts when there are no DUTs recorded."""
|
|
inventory = create_inventory({})
|
|
self.assertEqual(inventory.get_num_duts(), 0)
|
|
self.assertEqual(inventory.get_boards(), set())
|
|
self._check_inventory_counts(inventory, {})
|
|
self.assertEqual(inventory.get_num_models(), 0)
|
|
|
|
def _check_model_count(self, model_count):
|
|
"""Parameterized test for testing a specific number of models."""
|
|
msg = '[model: %d]' % (model_count,)
|
|
models = self._MODEL_LIST[:model_count]
|
|
data = {
|
|
m: PoolStatusCounts(
|
|
StatusCounts(1, 1, 1),
|
|
StatusCounts(1, 1, 1),
|
|
)
|
|
for m in models
|
|
}
|
|
inventory = create_inventory(data)
|
|
self.assertEqual(inventory.get_num_duts(), 6 * model_count, msg)
|
|
self.assertEqual(inventory.get_num_models(), model_count, msg)
|
|
for pool in [CRITICAL_POOL, SPARE_POOL]:
|
|
self.assertEqual(set(inventory.get_pool_models(pool)),
|
|
set(models))
|
|
self._check_inventory_counts(inventory, data, msg=msg)
|
|
|
|
def test_model_counts(self):
|
|
"""Test counts for various numbers of models."""
|
|
self.longMessage = True
|
|
for model_count in range(0, len(self._MODEL_LIST)):
|
|
self._check_model_count(model_count)
|
|
|
|
def _check_single_dut_counts(self, critical, spare):
|
|
"""Parmeterized test for single dut counts."""
|
|
self.longMessage = True
|
|
counts = PoolStatusCounts(critical, spare)
|
|
model = self._MODEL_LIST[0]
|
|
data = {model: counts}
|
|
msg = '[data: %s]' % (data,)
|
|
inventory = create_inventory(data)
|
|
self.assertEqual(inventory.get_num_duts(), 1, msg)
|
|
self.assertEqual(inventory.get_num_models(), 1, msg)
|
|
self._check_inventory_counts(inventory, data, msg=msg)
|
|
|
|
def test_single_dut_counts(self):
|
|
"""Test counts when there is a single DUT per board, and it is good."""
|
|
status_100 = StatusCounts(1, 0, 0)
|
|
status_010 = StatusCounts(0, 1, 0)
|
|
status_001 = StatusCounts(0, 0, 1)
|
|
status_null = StatusCounts(0, 0, 0)
|
|
self._check_single_dut_counts(status_100, status_null)
|
|
self._check_single_dut_counts(status_010, status_null)
|
|
self._check_single_dut_counts(status_001, status_null)
|
|
self._check_single_dut_counts(status_null, status_100)
|
|
self._check_single_dut_counts(status_null, status_010)
|
|
self._check_single_dut_counts(status_null, status_001)
|
|
|
|
|
|
# MODEL_MESSAGE_TEMPLATE -
|
|
# This is a sample of the output text produced by
|
|
# _generate_model_inventory_message(). This string is parsed by the
|
|
# tests below to construct a sample inventory that should produce
|
|
# the output, and then the output is generated and checked against
|
|
# this original sample.
|
|
#
|
|
# Constructing inventories from parsed sample text serves two
|
|
# related purposes:
|
|
# - It provides a way to see what the output should look like
|
|
# without having to run the script.
|
|
# - It helps make sure that a human being will actually look at
|
|
# the output to see that it's basically readable.
|
|
# This should also help prevent test bugs caused by writing tests
|
|
# that simply parrot the original output generation code.
|
|
|
|
_MODEL_MESSAGE_TEMPLATE = '''
|
|
Model Avail Bad Idle Good Spare Total
|
|
lion -1 13 2 11 12 26
|
|
tiger -1 5 2 9 4 16
|
|
bear 0 5 2 10 5 17
|
|
platypus 4 2 2 20 6 24
|
|
aardvark 7 2 2 6 9 10
|
|
'''
|
|
|
|
|
|
class PoolSetInventoryTests(unittest.TestCase):
|
|
"""Tests for `_generate_model_inventory_message()`.
|
|
|
|
The tests create various test inventories designed to match the
|
|
counts in `_MODEL_MESSAGE_TEMPLATE`, and asserts that the
|
|
generated message text matches the original message text.
|
|
|
|
Message text is represented as a list of strings, split on the
|
|
`'\n'` separator.
|
|
"""
|
|
|
|
def setUp(self):
|
|
self.maxDiff = None
|
|
lines = [x.strip() for x in _MODEL_MESSAGE_TEMPLATE.split('\n') if
|
|
x.strip()]
|
|
self._header, self._model_lines = lines[0], lines[1:]
|
|
self._model_data = []
|
|
for l in self._model_lines:
|
|
items = l.split()
|
|
model = items[0]
|
|
bad, idle, good, spare = [int(x) for x in items[2:-1]]
|
|
self._model_data.append((model, (good, bad, idle, spare)))
|
|
|
|
def _make_minimum_spares(self, counts):
|
|
"""Create a counts tuple with as few spare DUTs as possible."""
|
|
good, bad, idle, spares = counts
|
|
if spares > bad + idle:
|
|
return PoolStatusCounts(
|
|
StatusCounts(good + bad +idle - spares, 0, 0),
|
|
StatusCounts(spares - bad - idle, bad, idle),
|
|
)
|
|
elif spares < bad:
|
|
return PoolStatusCounts(
|
|
StatusCounts(good, bad - spares, idle),
|
|
StatusCounts(0, spares, 0),
|
|
)
|
|
else:
|
|
return PoolStatusCounts(
|
|
StatusCounts(good, 0, idle + bad - spares),
|
|
StatusCounts(0, bad, spares - bad),
|
|
)
|
|
|
|
def _make_maximum_spares(self, counts):
|
|
"""Create a counts tuple with as many spare DUTs as possible."""
|
|
good, bad, idle, spares = counts
|
|
if good > spares:
|
|
return PoolStatusCounts(
|
|
StatusCounts(good - spares, bad, idle),
|
|
StatusCounts(spares, 0, 0),
|
|
)
|
|
elif good + bad > spares:
|
|
return PoolStatusCounts(
|
|
StatusCounts(0, good + bad - spares, idle),
|
|
StatusCounts(good, spares - good, 0),
|
|
)
|
|
else:
|
|
return PoolStatusCounts(
|
|
StatusCounts(0, 0, good + bad + idle - spares),
|
|
StatusCounts(good, bad, spares - good - bad),
|
|
)
|
|
|
|
def _check_message(self, message):
|
|
"""Checks that message approximately matches expected string."""
|
|
message = [x.strip() for x in message.split('\n') if x.strip()]
|
|
self.assertIn(self._header, message)
|
|
body = message[message.index(self._header) + 1:]
|
|
self.assertEqual(body, self._model_lines)
|
|
|
|
def test_minimum_spares(self):
|
|
"""Test message generation when the spares pool is low."""
|
|
data = {
|
|
model: self._make_minimum_spares(counts)
|
|
for model, counts in self._model_data
|
|
}
|
|
inventory = create_inventory(data)
|
|
message = lab_inventory._generate_model_inventory_message(inventory)
|
|
self._check_message(message)
|
|
|
|
def test_maximum_spares(self):
|
|
"""Test message generation when the critical pool is low."""
|
|
data = {
|
|
model: self._make_maximum_spares(counts)
|
|
for model, counts in self._model_data
|
|
}
|
|
inventory = create_inventory(data)
|
|
message = lab_inventory._generate_model_inventory_message(inventory)
|
|
self._check_message(message)
|
|
|
|
def test_ignore_no_spares(self):
|
|
"""Test that messages ignore models with no spare pool."""
|
|
data = {
|
|
model: self._make_maximum_spares(counts)
|
|
for model, counts in self._model_data
|
|
}
|
|
data['elephant'] = ((5, 4, 0), (0, 0, 0))
|
|
inventory = create_inventory(data)
|
|
message = lab_inventory._generate_model_inventory_message(inventory)
|
|
self._check_message(message)
|
|
|
|
def test_ignore_no_critical(self):
|
|
"""Test that messages ignore models with no critical pools."""
|
|
data = {
|
|
model: self._make_maximum_spares(counts)
|
|
for model, counts in self._model_data
|
|
}
|
|
data['elephant'] = ((0, 0, 0), (1, 5, 1))
|
|
inventory = create_inventory(data)
|
|
message = lab_inventory._generate_model_inventory_message(inventory)
|
|
self._check_message(message)
|
|
|
|
def test_ignore_no_bad(self):
|
|
"""Test that messages ignore models with no bad DUTs."""
|
|
data = {
|
|
model: self._make_maximum_spares(counts)
|
|
for model, counts in self._model_data
|
|
}
|
|
data['elephant'] = ((5, 0, 1), (5, 0, 1))
|
|
inventory = create_inventory(data)
|
|
message = lab_inventory._generate_model_inventory_message(inventory)
|
|
self._check_message(message)
|
|
|
|
|
|
class _PoolInventoryTestBase(unittest.TestCase):
|
|
"""Parent class for tests relating to generating pool inventory messages.
|
|
|
|
Func `setUp` in the class parses a given |message_template| to obtain
|
|
header and body.
|
|
"""
|
|
|
|
def _read_template(self, message_template):
|
|
"""Read message template for PoolInventoryTest and IdleInventoryTest.
|
|
|
|
@param message_template: the input template to be parsed into: header
|
|
and content (report_lines).
|
|
"""
|
|
message_lines = message_template.split('\n')
|
|
self._header = message_lines[1]
|
|
self._report_lines = message_lines[2:-1]
|
|
|
|
def _check_report_no_info(self, text):
|
|
"""Test a message body containing no reported info.
|
|
|
|
The input `text` was created from a query to an inventory, which
|
|
has no objects meet the query and leads to an `empty` return.
|
|
Assert that the text consists of a single line starting with '('
|
|
and ending with ')'.
|
|
|
|
@param text: Message body text to be tested.
|
|
"""
|
|
self.assertTrue(len(text) == 1 and
|
|
text[0][0] == '(' and
|
|
text[0][-1] == ')')
|
|
|
|
def _check_report(self, text):
|
|
"""Test a message against the passed |expected_content|.
|
|
|
|
@param text: Message body text to be tested.
|
|
@param expected_content: The ground-truth content to be compared with.
|
|
"""
|
|
self.assertEqual(text, self._report_lines)
|
|
|
|
|
|
# _POOL_MESSAGE_TEMPLATE -
|
|
# This is a sample of the output text produced by
|
|
# _generate_pool_inventory_message(). This string is parsed by the
|
|
# tests below to construct a sample inventory that should produce
|
|
# the output, and then the output is generated and checked against
|
|
# this original sample.
|
|
#
|
|
# See the comments on _BOARD_MESSAGE_TEMPLATE above for the
|
|
# rationale on using sample text in this way.
|
|
|
|
_POOL_MESSAGE_TEMPLATE = '''
|
|
Model Bad Idle Good Total
|
|
lion 5 2 6 13
|
|
tiger 4 1 5 10
|
|
bear 3 0 7 10
|
|
aardvark 2 0 0 2
|
|
platypus 1 1 1 3
|
|
'''
|
|
|
|
_POOL_ADMIN_URL = 'http://go/cros-manage-duts'
|
|
|
|
|
|
class PoolInventoryTests(_PoolInventoryTestBase):
|
|
"""Tests for `_generate_pool_inventory_message()`.
|
|
|
|
The tests create various test inventories designed to match the
|
|
counts in `_POOL_MESSAGE_TEMPLATE`, and assert that the
|
|
generated message text matches the format established in the
|
|
original message text.
|
|
|
|
The output message text is parsed against the following grammar:
|
|
<message> -> <intro> <pool> { "blank line" <pool> }
|
|
<intro> ->
|
|
Instructions to depty mentioning the admin page URL
|
|
A blank line
|
|
<pool> ->
|
|
<description>
|
|
<header line>
|
|
<message body>
|
|
<description> ->
|
|
Any number of lines describing one pool
|
|
<header line> ->
|
|
The header line from `_POOL_MESSAGE_TEMPLATE`
|
|
<message body> ->
|
|
Any number of non-blank lines
|
|
|
|
After parsing messages into the parts described above, various
|
|
assertions are tested against the parsed output, including
|
|
that the message body matches the body from
|
|
`_POOL_MESSAGE_TEMPLATE`.
|
|
|
|
Parse message text is represented as a list of strings, split on
|
|
the `'\n'` separator.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(PoolInventoryTests, self)._read_template(_POOL_MESSAGE_TEMPLATE)
|
|
self._model_data = []
|
|
for l in self._report_lines:
|
|
items = l.split()
|
|
model = items[0]
|
|
bad = int(items[1])
|
|
idle = int(items[2])
|
|
good = int(items[3])
|
|
self._model_data.append((model, (good, bad, idle)))
|
|
|
|
def _create_histories(self, pools, model_data):
|
|
"""Return a list suitable to create a `_LabInventory` object.
|
|
|
|
Creates a list of `_FakeHostHistory` objects that can be
|
|
used to create a lab inventory. `pools` is a list of strings
|
|
naming pools, and `model_data` is a list of tuples of the
|
|
form
|
|
`(model, (goodcount, badcount))`
|
|
where
|
|
`model` is a model name.
|
|
`goodcount` is the number of working DUTs in the pool.
|
|
`badcount` is the number of broken DUTs in the pool.
|
|
|
|
@param pools List of pools for which to create
|
|
histories.
|
|
@param model_data List of tuples containing models and DUT
|
|
counts.
|
|
@return A list of `_FakeHostHistory` objects that can be
|
|
used to create a `_LabInventory` object.
|
|
"""
|
|
histories = []
|
|
status_choices = (_WORKING, _BROKEN, _UNUSED)
|
|
for pool in pools:
|
|
for model, counts in model_data:
|
|
for status, count in zip(status_choices, counts):
|
|
for x in range(0, count):
|
|
histories.append(
|
|
_FakeHostHistory(model, pool, status))
|
|
return histories
|
|
|
|
def _parse_pool_summaries(self, histories):
|
|
"""Parse message output according to the grammar above.
|
|
|
|
Create a lab inventory from the given `histories`, and
|
|
generate the pool inventory message. Then parse the message
|
|
and return a dictionary mapping each pool to the message
|
|
body parsed after that pool.
|
|
|
|
Tests the following assertions:
|
|
* Each <description> contains a mention of exactly one
|
|
pool in the `CRITICAL_POOLS` list.
|
|
* Each pool is mentioned in exactly one <description>.
|
|
Note that the grammar requires the header to appear once
|
|
for each pool, so the parsing implicitly asserts that the
|
|
output contains the header.
|
|
|
|
@param histories Input used to create the test
|
|
`_LabInventory` object.
|
|
@return A dictionary mapping model names to the output
|
|
(a list of lines) for the model.
|
|
"""
|
|
inventory = lab_inventory._LabInventory(
|
|
histories, lab_inventory.MANAGED_POOLS)
|
|
message = lab_inventory._generate_pool_inventory_message(
|
|
inventory).split('\n')
|
|
poolset = set(lab_inventory.CRITICAL_POOLS)
|
|
seen_url = False
|
|
seen_intro = False
|
|
description = ''
|
|
model_text = {}
|
|
current_pool = None
|
|
for line in message:
|
|
if not seen_url:
|
|
if _POOL_ADMIN_URL in line:
|
|
seen_url = True
|
|
elif not seen_intro:
|
|
if not line:
|
|
seen_intro = True
|
|
elif current_pool is None:
|
|
if line == self._header:
|
|
pools_mentioned = [p for p in poolset
|
|
if p in description]
|
|
self.assertEqual(len(pools_mentioned), 1)
|
|
current_pool = pools_mentioned[0]
|
|
description = ''
|
|
model_text[current_pool] = []
|
|
poolset.remove(current_pool)
|
|
else:
|
|
description += line
|
|
else:
|
|
if line:
|
|
model_text[current_pool].append(line)
|
|
else:
|
|
current_pool = None
|
|
self.assertEqual(len(poolset), 0)
|
|
return model_text
|
|
|
|
def test_no_shortages(self):
|
|
"""Test correct output when no pools have shortages."""
|
|
model_text = self._parse_pool_summaries([])
|
|
for text in model_text.values():
|
|
self._check_report_no_info(text)
|
|
|
|
def test_one_pool_shortage(self):
|
|
"""Test correct output when exactly one pool has a shortage."""
|
|
for pool in lab_inventory.CRITICAL_POOLS:
|
|
histories = self._create_histories((pool,),
|
|
self._model_data)
|
|
model_text = self._parse_pool_summaries(histories)
|
|
for checkpool in lab_inventory.CRITICAL_POOLS:
|
|
text = model_text[checkpool]
|
|
if checkpool == pool:
|
|
self._check_report(text)
|
|
else:
|
|
self._check_report_no_info(text)
|
|
|
|
def test_all_pool_shortages(self):
|
|
"""Test correct output when all pools have a shortage."""
|
|
histories = []
|
|
for pool in lab_inventory.CRITICAL_POOLS:
|
|
histories.extend(
|
|
self._create_histories((pool,),
|
|
self._model_data))
|
|
model_text = self._parse_pool_summaries(histories)
|
|
for pool in lab_inventory.CRITICAL_POOLS:
|
|
self._check_report(model_text[pool])
|
|
|
|
def test_full_model_ignored(self):
|
|
"""Test that models at full strength are not reported."""
|
|
pool = lab_inventory.CRITICAL_POOLS[0]
|
|
full_model = [('echidna', (5, 0, 0))]
|
|
histories = self._create_histories((pool,),
|
|
full_model)
|
|
text = self._parse_pool_summaries(histories)[pool]
|
|
self._check_report_no_info(text)
|
|
model_data = self._model_data + full_model
|
|
histories = self._create_histories((pool,), model_data)
|
|
text = self._parse_pool_summaries(histories)[pool]
|
|
self._check_report(text)
|
|
|
|
def test_spare_pool_ignored(self):
|
|
"""Test that reporting ignores the spare pool inventory."""
|
|
spare_pool = lab_inventory.SPARE_POOL
|
|
spare_data = self._model_data + [('echidna', (0, 5, 0))]
|
|
histories = self._create_histories((spare_pool,),
|
|
spare_data)
|
|
model_text = self._parse_pool_summaries(histories)
|
|
for pool in lab_inventory.CRITICAL_POOLS:
|
|
self._check_report_no_info(model_text[pool])
|
|
|
|
|
|
_IDLE_MESSAGE_TEMPLATE = '''
|
|
Hostname Model Pool
|
|
chromeos4-row12-rack4-host7 tiger bvt
|
|
chromeos1-row3-rack1-host2 lion bvt
|
|
chromeos3-row2-rack2-host5 lion cq
|
|
chromeos2-row7-rack3-host11 platypus suites
|
|
'''
|
|
|
|
|
|
class IdleInventoryTests(_PoolInventoryTestBase):
|
|
"""Tests for `_generate_idle_inventory_message()`.
|
|
|
|
The tests create idle duts that match the counts and pool in
|
|
`_IDLE_MESSAGE_TEMPLATE`. In test, it asserts that the generated
|
|
idle message text matches the format established in
|
|
`_IDLE_MESSAGE_TEMPLATE`.
|
|
|
|
Parse message text is represented as a list of strings, split on
|
|
the `'\n'` separator.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(IdleInventoryTests, self)._read_template(_IDLE_MESSAGE_TEMPLATE)
|
|
self._host_data = []
|
|
for h in self._report_lines:
|
|
items = h.split()
|
|
hostname = items[0]
|
|
model = items[1]
|
|
pool = items[2]
|
|
self._host_data.append((hostname, model, pool))
|
|
self._histories = []
|
|
self._histories.append(_FakeHostHistory('echidna', 'bvt', _BROKEN))
|
|
self._histories.append(_FakeHostHistory('lion', 'bvt', _WORKING))
|
|
|
|
def _add_idles(self):
|
|
"""Add idle duts from `_IDLE_MESSAGE_TEMPLATE`."""
|
|
idle_histories = [_FakeHostHistory(
|
|
model, pool, _UNUSED, hostname)
|
|
for hostname, model, pool in self._host_data]
|
|
self._histories.extend(idle_histories)
|
|
|
|
def _check_header(self, text):
|
|
"""Check whether header in the template `_IDLE_MESSAGE_TEMPLATE` is in
|
|
passed text."""
|
|
self.assertIn(self._header, text)
|
|
|
|
def _get_idle_message(self, histories):
|
|
"""Generate idle inventory and obtain its message.
|
|
|
|
@param histories: Used to create lab inventory.
|
|
|
|
@return the generated idle message.
|
|
"""
|
|
inventory = lab_inventory._LabInventory(
|
|
histories, lab_inventory.MANAGED_POOLS)
|
|
message = lab_inventory._generate_idle_inventory_message(
|
|
inventory).split('\n')
|
|
return message
|
|
|
|
def test_check_idle_inventory(self):
|
|
"""Test that reporting all the idle DUTs for every pool, sorted by
|
|
lab_inventory.MANAGED_POOLS.
|
|
"""
|
|
self._add_idles()
|
|
|
|
message = self._get_idle_message(self._histories)
|
|
self._check_header(message)
|
|
self._check_report(message[message.index(self._header) + 1 :])
|
|
|
|
def test_no_idle_inventory(self):
|
|
"""Test that reporting no idle DUTs."""
|
|
message = self._get_idle_message(self._histories)
|
|
self._check_header(message)
|
|
self._check_report_no_info(
|
|
message[message.index(self._header) + 1 :])
|
|
|
|
|
|
class CommandParsingTests(unittest.TestCase):
|
|
"""Tests for command line argument parsing in `_parse_command()`."""
|
|
|
|
# At least one of these options must be specified on every command
|
|
# line; otherwise, the command line parsing will fail.
|
|
_REPORT_OPTIONS = [
|
|
'--model-notify=', '--pool-notify=', '--report-untestable'
|
|
]
|
|
|
|
def setUp(self):
|
|
dirpath = '/usr/local/fubar'
|
|
self._command_path = os.path.join(dirpath,
|
|
'site_utils',
|
|
'arglebargle')
|
|
self._logdir = os.path.join(dirpath, lab_inventory._LOGDIR)
|
|
|
|
def _parse_arguments(self, argv):
|
|
"""Test parsing with explictly passed report options."""
|
|
full_argv = [self._command_path] + argv
|
|
return lab_inventory._parse_command(full_argv)
|
|
|
|
def _parse_non_report_arguments(self, argv):
|
|
"""Test parsing for non-report command-line options."""
|
|
return self._parse_arguments(argv + self._REPORT_OPTIONS)
|
|
|
|
def _check_non_report_defaults(self, report_option):
|
|
arguments = self._parse_arguments([report_option])
|
|
self.assertEqual(arguments.duration,
|
|
lab_inventory._DEFAULT_DURATION)
|
|
self.assertIsNone(arguments.recommend)
|
|
self.assertFalse(arguments.debug)
|
|
self.assertEqual(arguments.logdir, self._logdir)
|
|
self.assertEqual(arguments.modelnames, [])
|
|
return arguments
|
|
|
|
def test_empty_arguments(self):
|
|
"""Test that no reports requested is an error."""
|
|
arguments = self._parse_arguments([])
|
|
self.assertIsNone(arguments)
|
|
|
|
def test_argument_defaults(self):
|
|
"""Test that option defaults match expectations."""
|
|
for report in self._REPORT_OPTIONS:
|
|
arguments = self._check_non_report_defaults(report)
|
|
|
|
def test_model_notify_defaults(self):
|
|
"""Test defaults when `--model-notify` is specified alone."""
|
|
arguments = self._parse_arguments(['--model-notify='])
|
|
self.assertEqual(arguments.model_notify, [''])
|
|
self.assertEqual(arguments.pool_notify, [])
|
|
self.assertFalse(arguments.report_untestable)
|
|
|
|
def test_pool_notify_defaults(self):
|
|
"""Test defaults when `--pool-notify` is specified alone."""
|
|
arguments = self._parse_arguments(['--pool-notify='])
|
|
self.assertEqual(arguments.model_notify, [])
|
|
self.assertEqual(arguments.pool_notify, [''])
|
|
self.assertFalse(arguments.report_untestable)
|
|
|
|
def test_report_untestable_defaults(self):
|
|
"""Test defaults when `--report-untestable` is specified alone."""
|
|
arguments = self._parse_arguments(['--report-untestable'])
|
|
self.assertEqual(arguments.model_notify, [])
|
|
self.assertEqual(arguments.pool_notify, [])
|
|
self.assertTrue(arguments.report_untestable)
|
|
|
|
def test_model_arguments(self):
|
|
"""Test that non-option arguments are returned in `modelnames`."""
|
|
modellist = ['aardvark', 'echidna']
|
|
arguments = self._parse_non_report_arguments(modellist)
|
|
self.assertEqual(arguments.modelnames, modellist)
|
|
|
|
def test_recommend_option(self):
|
|
"""Test parsing of the `--recommend` option."""
|
|
for opt in ['-r', '--recommend']:
|
|
for recommend in ['5', '55']:
|
|
arguments = self._parse_non_report_arguments([opt, recommend])
|
|
self.assertEqual(arguments.recommend, int(recommend))
|
|
|
|
def test_debug_option(self):
|
|
"""Test parsing of the `--debug` option."""
|
|
arguments = self._parse_non_report_arguments(['--debug'])
|
|
self.assertTrue(arguments.debug)
|
|
|
|
def test_duration(self):
|
|
"""Test parsing of the `--duration` option."""
|
|
for opt in ['-d', '--duration']:
|
|
for duration in ['1', '11']:
|
|
arguments = self._parse_non_report_arguments([opt, duration])
|
|
self.assertEqual(arguments.duration, int(duration))
|
|
|
|
def _check_email_option(self, option, getlist):
|
|
"""Test parsing of e-mail address options.
|
|
|
|
This is a helper function to test the `--model-notify` and
|
|
`--pool-notify` options. It tests the following cases:
|
|
* `--option a1` gives the list [a1]
|
|
* `--option ' a1 '` gives the list [a1]
|
|
* `--option a1 --option a2` gives the list [a1, a2]
|
|
* `--option a1,a2` gives the list [a1, a2]
|
|
* `--option 'a1, a2'` gives the list [a1, a2]
|
|
|
|
@param option The option to be tested.
|
|
@param getlist A function to return the option's value from
|
|
parsed command line arguments.
|
|
"""
|
|
a1 = 'mumble@mumbler.com'
|
|
a2 = 'bumble@bumbler.org'
|
|
arguments = self._parse_arguments([option, a1])
|
|
self.assertEqual(getlist(arguments), [a1])
|
|
arguments = self._parse_arguments([option, ' ' + a1 + ' '])
|
|
self.assertEqual(getlist(arguments), [a1])
|
|
arguments = self._parse_arguments([option, a1, option, a2])
|
|
self.assertEqual(getlist(arguments), [a1, a2])
|
|
arguments = self._parse_arguments(
|
|
[option, ','.join([a1, a2])])
|
|
self.assertEqual(getlist(arguments), [a1, a2])
|
|
arguments = self._parse_arguments(
|
|
[option, ', '.join([a1, a2])])
|
|
self.assertEqual(getlist(arguments), [a1, a2])
|
|
|
|
def test_model_notify(self):
|
|
"""Test parsing of the `--model-notify` option."""
|
|
self._check_email_option('--model-notify',
|
|
lambda a: a.model_notify)
|
|
|
|
def test_pool_notify(self):
|
|
"""Test parsing of the `--pool-notify` option."""
|
|
self._check_email_option('--pool-notify',
|
|
lambda a: a.pool_notify)
|
|
|
|
def test_logdir_option(self):
|
|
"""Test parsing of the `--logdir` option."""
|
|
logdir = '/usr/local/whatsis/logs'
|
|
arguments = self._parse_non_report_arguments(['--logdir', logdir])
|
|
self.assertEqual(arguments.logdir, logdir)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Some of the functions we test log messages. Prevent those
|
|
# messages from showing up in test output.
|
|
logging.getLogger().setLevel(logging.CRITICAL)
|
|
unittest.main()
|