blob: d040265433cc507405758145fac3e07d6215510f [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# pylint: disable=protected-access
import logging
import unittest
from unittest import mock
from cros.factory.device import device_utils
from cros.factory.goofy import invocation
from cros.factory.test.fixture import bft_fixture
from cros.factory.test.i18n import _
from cros.factory.test.pytests import summary
from cros.factory.test import state
from cros.factory.test import test_case
from cros.factory.test.test_lists import manager
from cros.factory.test import test_ui
from cros.factory.utils import type_utils
MOCK_TEST_LIST = manager.BuildTestListForUnittest({
'tests': [{
'id':
'Root',
'subtests': [
{
'id': 'Test1',
'pytest_name': 'test_1'
},
{
'id': 'Test2',
'pytest_name': 'test_2'
},
{
'id':
'TestGroup',
'subtests': [{
'id': 'Test3',
'pytest_name': 'test_3'
}, {
'id': 'Test4',
'pytest_name': 'test_4'
}, {
'id': 'Test5',
'pytest_name': 'test_5'
}, {
'id': 'Summary',
'pytest_name': 'summary'
}]
},
]
}]
})
TEST_INFO_PATH = 'test:Root.TestGroup.Summary'
class FakeArgs:
def __init__(self, **kwargs):
# yapf: disable
self.prompt_message: str = _('Click or press SPACE to continue') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.disable_input_on_fail: bool = False # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.pass_without_prompt: bool = False # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.bft_fixture: dict = None # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.accessibility: bool = False # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.include_parents: bool = False # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.run_factory_external_name: str = None # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.screensaver_timeout: int = None # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
for k, v in kwargs.items():
setattr(self, k, v)
class SummaryUnitTest(unittest.TestCase):
def setUp(self):
# Disable root logger to avoid redundant logs from other module.
logging.getLogger().disabled = True
self.test = summary.Report()
self.ui = mock.create_autospec(test_ui.StandardUI)
type_utils.LazyProperty.Override(self.test, 'ui', self.ui)
self.mock_frontend_proxy = self.ui.InitJSTestObject.return_value
# yapf: disable
self.test.test_info = mock.create_autospec(invocation.PytestInfo) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.test.test_info.ReadTestList.return_value = MOCK_TEST_LIST # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
# yapf: disable
self.test.test_info.path = TEST_INFO_PATH # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
patcher = mock.patch.object(device_utils, 'CreateDUTInterface',
autospec=True)
self.mock_dut = patcher.start().return_value
patcher = mock.patch.object(state, 'GetInstance', autospec=True)
self.mock_goofy = patcher.start().return_value
patcher = mock.patch.object(type_utils, 'Obj', autospec=True)
self.mock_obj = patcher.start()
self.mock_obj.return_value.status = None
patcher = mock.patch.object(state.TestState, 'OverallStatus', autospec=True,
return_value=state.TestState.PASSED)
self.mock_overall_status = patcher.start()
patcher = mock.patch.object(test_case.TestCase, 'WaitTaskEnd',
autospec=True)
self.mock_wait_task_end = patcher.start()
self.addCleanup(mock.patch.stopall)
# yapf: disable
self.test.args = FakeArgs() # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
def test_setUp_InitFrontendProxy(self):
# Turn on the screensaver after 5 seconds.
# yapf: disable
self.test.args = FakeArgs(screensaver_timeout=5) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
# Disable the screensaver.
# yapf: disable
self.test.args = FakeArgs(screensaver_timeout=None) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.ui.InitJSTestObject.assert_has_calls([
mock.call('SummaryTest', 5),
mock.call('SummaryTest', None),
])
def test_setUp_TimeoutShouldNotBeZero(self):
# yapf: disable
self.test.args = FakeArgs(screensaver_timeout=0) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
with self.assertRaisesRegex(
AssertionError, r"False is not true : "
r"Timeout for screensaver should be positive\."):
self.test.setUp()
def test_GetTestResults_IncludeParent(self):
mock_test_list = MOCK_TEST_LIST
mock_test = mock_test_list.LookupPath(TEST_INFO_PATH)
mock_state = mock.Mock()
mock_state.get.return_value.status = 'fake_status_from_state'
# yapf: disable
self.test.args = FakeArgs(include_parents=True) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
test_results = self.test._GetTestResults(mock_test, mock_state)
self.mock_obj.assert_has_calls([
mock.call(path='test:Root.Test1', label={'en-US': 'Test 1'},
status='fake_status_from_state'),
mock.call(path='test:Root.Test2', label={'en-US': 'Test 2'},
status='fake_status_from_state'),
mock.call(path='test:Root.TestGroup.Test3', label={'en-US': 'Test 3'},
status='fake_status_from_state'),
mock.call(path='test:Root.TestGroup.Test4', label={'en-US': 'Test 4'},
status='fake_status_from_state'),
mock.call(path='test:Root.TestGroup.Test5', label={'en-US': 'Test 5'},
status='fake_status_from_state')
])
self.assertEqual(test_results, [self.mock_obj.return_value] * 5)
def test_GetTestResults_NotIncludeParent(self):
mock_test_list = MOCK_TEST_LIST
mock_test = mock_test_list.LookupPath(TEST_INFO_PATH)
mock_state = mock.Mock()
mock_state.get.return_value.status = 'fake_status_from_state'
# yapf: disable
self.test.args = FakeArgs(include_parents=False) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
test_results = self.test._GetTestResults(mock_test, mock_state)
self.mock_obj.assert_has_calls([
mock.call(path='test:Root.TestGroup.Test3', label={'en-US': 'Test 3'},
status='fake_status_from_state'),
mock.call(path='test:Root.TestGroup.Test4', label={'en-US': 'Test 4'},
status='fake_status_from_state'),
mock.call(path='test:Root.TestGroup.Test5', label={'en-US': 'Test 5'},
status='fake_status_from_state')
])
self.assertEqual(test_results, [self.mock_obj.return_value] * 3)
@mock.patch.object(summary.Report, '_GetTestResults', autospec=True)
@mock.patch.object(summary.Report, '_ShowTestInfoAtFrontend', autospec=True)
def test_GetTestResults_InteractWithOtherFunction(self, mock_show_test_info,
mock_get_test_results):
self.test.setUp()
self.test.runTest()
self.assertEqual(mock_show_test_info.call_args.args[3],
mock_get_test_results.return_value)
@mock.patch.object(summary.Report, '_GetTestResults', autospec=True)
@mock.patch.object(summary.Report, '_ShowTestInfoAtFrontend', autospec=True)
def test_runTest_GetOverallStatus(self, mock_show_test_info,
mock_get_test_results):
self.mock_overall_status.return_value = 'fake_status'
mock_result = mock.Mock()
mock_get_test_results.return_value = [mock_result] * 3
self.test.setUp()
with self.assertLogs(level='INFO') as log:
logging.getLogger().disabled = False
self.test.runTest()
logging.getLogger().disabled = True
self.assertTrue(
any("overall_status='fake_status'" in msg for msg in log.output))
self.mock_overall_status.assert_called_once_with([mock_result.status] * 3)
self.assertEqual(mock_show_test_info.call_args.args[2], 'fake_status')
@mock.patch.object(bft_fixture, 'CreateBFTFixture', autospec=True)
def test_SetFixtureStatusLight(self, mock_create_fixture):
fake_bit_fixture = {
'class_name': 'fake_name',
'params': {}
}
# yapf: disable
self.test.args = FakeArgs(bft_fixture=fake_bit_fixture) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
mock_fixture = mock_create_fixture.return_value
# All tests passed.
self.test.setUp()
self.test._SetFixtureStatusLight(all_pass=True)
# Some tests failed.
self.test.setUp()
self.test._SetFixtureStatusLight(all_pass=False)
mock_create_fixture.assert_has_calls(
[mock.call(**fake_bit_fixture),
mock.call(**fake_bit_fixture)], any_order=True)
mock_fixture.SetStatusColor.assert_has_calls([
mock.call(mock_fixture.StatusColor.GREEN),
mock.call(mock_fixture.StatusColor.RED)
])
assert mock_fixture.Disconnect.call_count == 2
@mock.patch.object(bft_fixture, 'CreateBFTFixture', autospec=True)
def test_SetFixtureStatusLight_Fail(self, mock_create_fixture):
# yapf: disable
self.test.args = FakeArgs(bft_fixture={ # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'class_name': 'fake_name',
'params': {}
})
mock_fixture = mock_create_fixture.return_value
mock_fixture.SetStatusColor.side_effect = bft_fixture.BFTFixtureException
self.test.setUp()
with self.assertLogs(level='ERROR') as log:
logging.getLogger().disabled = False
self.test._SetFixtureStatusLight(all_pass=True)
logging.getLogger().disabled = True
self.assertTrue(
'Unable to set status color on BFT fixture' in log.output[0])
@mock.patch.object(summary.Report, '_SetFixtureStatusLight', autospec=True)
def test_runTest_SetFixtureStatusLightWhenBFTFixtureIsSet(
self, mock_set_fixture_status_light):
# yapf: disable
self.test.args = FakeArgs(bft_fixture={ # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
'class_name': 'fake_name',
'params': {}
})
self.test.setUp()
self.test.runTest()
mock_set_fixture_status_light.assert_called_once()
def test_WriteResultFile_GetFilePath(self):
# yapf: disable
self.test.args = FakeArgs(run_factory_external_name='fake_result_file') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._WriteResultFile(all_pass=True, test_results=[])
self.mock_dut.CheckCall.assert_called_once_with(
['mkdir', '-p', '/run/factory/external'])
self.mock_dut.path.join.assert_called_once_with('/run/factory/external',
'fake_result_file')
self.assertEqual(self.mock_dut.WriteFile.call_args.args[0],
self.mock_dut.path.join.return_value)
def test_WriteResultFile_TestPass(self):
# yapf: disable
self.test.args = FakeArgs(run_factory_external_name='fake_result_file') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._WriteResultFile(all_pass=True, test_results=[])
self.mock_dut.WriteFile.assert_called_once_with(
self.mock_dut.path.join.return_value, 'PASS')
def test_WriteResultFile_TestFail(self):
# yapf: disable
self.test.args = FakeArgs(run_factory_external_name='fake_result_file') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._WriteResultFile(
all_pass=False,
test_results=[mock.Mock(path='fake_path', status='fake_status')])
self.mock_dut.WriteFile.assert_called_once_with(
self.mock_dut.path.join.return_value, 'fake_path: fake_status\n')
@mock.patch.object(summary.Report, '_WriteResultFile', autospec=True)
def test_runTest_WriteResultFileWhenExternalNameIsSet(self,
mock_write_result_file):
# yapf: disable
self.test.args = FakeArgs(run_factory_external_name='fake_result_file') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test.runTest()
mock_write_result_file.assert_called_once()
def test_runTest_FinishTestWhenAllTestsPassAndWithoutPrompt(self):
# yapf: disable
self.test.args = FakeArgs(pass_without_prompt=True) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.mock_overall_status.return_value = state.TestState.PASSED
self.test.setUp()
self.test.runTest()
self.mock_frontend_proxy.SetPromptMessage.assert_not_called()
def test_PromptMessage_TestsPass(self):
# yapf: disable
self.test.args = FakeArgs(pass_without_prompt=False, prompt_message='msg') # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._PromptMessage(all_pass=True)
self.mock_frontend_proxy.SetPromptMessage.assert_called_once_with(
'msg', True)
def test_PromptMessage_TestsFailAndEnableInputOnFail(self):
# yapf: disable
self.test.args = FakeArgs(prompt_message='msg', disable_input_on_fail=False) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._PromptMessage(all_pass=False)
self.mock_frontend_proxy.SetPromptMessage.assert_called_once_with(
'msg', True)
def test_PromptMessage_TestsFailAndDisableInputOnFail(self):
# yapf: disable
self.test.args = FakeArgs(disable_input_on_fail=True) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._PromptMessage(all_pass=False)
self.mock_frontend_proxy.SetPromptMessage.assert_called_once_with(
_('Unable to proceed, since some previous tests have not passed.'),
False)
def test_ShowTestInfoAtFrontend(self):
mock_test = mock.Mock()
fake_status = 'fake status'
fake_results = ['fake results']
self.test.setUp()
self.test._ShowTestInfoAtFrontend(mock_test, fake_status, fake_results)
self.mock_frontend_proxy.SetTestName.assert_called_once_with(
mock_test.parent.path)
self.mock_frontend_proxy.SetOverallTestStatus.assert_called_once_with(
fake_status)
self.mock_frontend_proxy.SetDetailTestResults.assert_called_once_with(
fake_results)
def test_BindUiKeys_EnableInputOnFail(self):
# yapf: disable
self.test.args = FakeArgs(disable_input_on_fail=False) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._BindUiKeys(all_pass=False)
self.ui.BindStandardKeys.assert_called_once()
def test_BindUiKeys_TestPassAndDisableInput(self):
# yapf: disable
self.test.args = FakeArgs(disable_input_on_fail=True) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._BindUiKeys(all_pass=True)
self.ui.BindStandardPassKeys.assert_called_once()
def test_BindUiKeys_TestFailAndDisableInput(self):
# yapf: disable
self.test.args = FakeArgs(disable_input_on_fail=True) # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
self.test.setUp()
self.test._BindUiKeys(all_pass=False)
self.ui.BindStandardKeys.assert_not_called()
self.ui.BindStandardPassKeys.assert_not_called()
def test_runTest_OverallStatusInPassedState(self):
# yapf: disable
self.test.args = FakeArgs( # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
bft_fixture={
'class_name': 'fake_name',
'params': {}
}, accessibility=True, run_factory_external_name='fake_file')
self.mock_overall_status.return_value = state.TestState.PASSED
with mock.patch.multiple(
self.test,
autospec=True,
_SetFixtureStatusLight=mock.DEFAULT,
_WriteResultFile=mock.DEFAULT,
_PromptMessage=mock.DEFAULT,
_BindUiKeys=mock.DEFAULT,
) as mock_methods:
self.test.setUp()
self.test.runTest()
self.assertEqual(self.mock_goofy.PostHookEvent.call_args.args[1], 'Good')
mock_methods['_SetFixtureStatusLight'].assert_called_once_with(True)
self.assertEqual(mock_methods['_WriteResultFile'].call_args.args[0], True)
mock_methods['_PromptMessage'].assert_called_once_with(True)
mock_methods['_BindUiKeys'].assert_called_once_with(True)
self.mock_frontend_proxy.EnableAccessibility.assert_not_called()
def test_runTest_OverallStatusNotInPassedState(self):
# yapf: disable
self.test.args = FakeArgs( # type: ignore #TODO(b/338318729) Fixit! # pylint: disable=line-too-long
# yapf: enable
bft_fixture={
'class_name': 'fake_name',
'params': {}
}, accessibility=True, run_factory_external_name='fake_file')
self.mock_overall_status.return_value = state.TestState.FAILED
with mock.patch.multiple(
self.test,
autospec=True,
_SetFixtureStatusLight=mock.DEFAULT,
_WriteResultFile=mock.DEFAULT,
_PromptMessage=mock.DEFAULT,
_BindUiKeys=mock.DEFAULT,
) as mock_methods:
self.test.setUp()
self.test.runTest()
self.assertEqual(self.mock_goofy.PostHookEvent.call_args.args[1], 'Bad')
mock_methods['_SetFixtureStatusLight'].assert_called_once_with(False)
self.assertEqual(mock_methods['_WriteResultFile'].call_args.args[0],
False)
mock_methods['_PromptMessage'].assert_called_once_with(False)
mock_methods['_BindUiKeys'].assert_called_once_with(False)
self.mock_frontend_proxy.EnableAccessibility.assert_called_once()
if __name__ == '__main__':
unittest.main()