blob: 58455d3593f6692e02307e7537eeca2b2ab472b7 [file] [log] [blame]
#!/usr/bin/env python3
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
import shutil
import tempfile
import unittest
import mock
from mobly import config_parser as mobly_config_parser
from acts import base_test
from acts import signals
from acts import test_decorators
from acts import test_runner
from acts.controllers.sl4a_lib import rpc_client
from acts.libs.testtracker.testtracker_results_writer import KEY_EFFORT_NAME
TEST_TRACKER_UUID = '12345'
UUID_KEY = 'uuid'
def return_true():
return True
def return_false():
return False
def raise_pass():
raise signals.TestPass('')
def raise_failure():
raise signals.TestFailure('')
def raise_sl4a():
raise rpc_client.Sl4aException('')
def raise_generic():
raise Exception('')
class TestDecoratorUnitTests(unittest.TestCase):
def _verify_test_info(self, func):
try:
test_decorators.test_info(uuid=TEST_TRACKER_UUID)(func)()
self.fail('Expected decorator to raise exception.')
except signals.TestSignal as e:
self.assertTrue(hasattr(e, 'extras'),
'Expected thrown exception to have extras.')
self.assertTrue(UUID_KEY in e.extras,
'Expected extras to have %s.' % UUID_KEY)
self.assertEqual(e.extras[UUID_KEY], TEST_TRACKER_UUID)
def test_test_info_on_return_true(self):
self._verify_test_info(return_true)
def test_test_info_on_return_false(self):
self._verify_test_info(return_false)
def test_test_info_on_raise_pass(self):
self._verify_test_info(raise_pass)
def test_test_info_on_raise_failure(self):
self._verify_test_info(raise_failure)
def test_test_info_on_raise_sl4a(self):
self._verify_test_info(raise_sl4a)
def test_test_info_on_raise_generic(self):
self._verify_test_info(raise_generic)
def test_test_tracker_info_on_raise_generic_is_chained(self):
@test_decorators.test_tracker_info(uuid='SOME_UID')
def raise_generic():
raise ValueError('I am a ValueError')
with self.assertRaises(signals.TestError) as context:
raise_generic()
self.assertIsInstance(context.exception.__cause__, ValueError)
def test_tti_returns_existing_test_pass(self):
@test_decorators.test_info(uuid='SOME_UID')
def test_raises_test_pass():
raise signals.TestPass('Expected Message')
with self.assertRaises(signals.TestPass) as context:
test_raises_test_pass()
self.assertEqual(context.exception.details, 'Expected Message')
def test_tti_returns_existing_test_failure(self):
@test_decorators.test_info(uuid='SOME_UID')
def test_raises_test_failure():
raise signals.TestFailure('Expected Message')
with self.assertRaises(signals.TestFailure) as context:
test_raises_test_failure()
self.assertEqual(context.exception.details, 'Expected Message')
def test_tti_returns_test_error_on_non_signal_error(self):
expected_error = ValueError('Some Message')
@test_decorators.test_info(uuid='SOME_UID')
def test_raises_non_signal():
raise expected_error
with self.assertRaises(signals.TestError) as context:
test_raises_non_signal()
self.assertEqual(context.exception.details, expected_error)
def test_tti_returns_test_pass_if_no_return_value_specified(self):
@test_decorators.test_info(uuid='SOME_UID')
def test_returns_nothing():
pass
with self.assertRaises(signals.TestPass):
test_returns_nothing()
def test_tti_returns_test_fail_if_return_value_is_truthy(self):
"""This is heavily frowned upon. Use signals.TestPass instead!"""
@test_decorators.test_info(uuid='SOME_UID')
def test_returns_truthy():
return True
with self.assertRaises(signals.TestPass):
test_returns_truthy()
def test_tti_returns_test_fail_if_return_value_is_falsy(self):
"""This is heavily frowned upon. Use signals.TestFailure instead!"""
@test_decorators.test_info(uuid='SOME_UID')
def test_returns_falsy_but_not_none():
return False
with self.assertRaises(signals.TestFailure):
test_returns_falsy_but_not_none()
def test_function_name(self):
"""Test test_func.__name__ returns its original unwrapped name"""
@test_decorators.test_info(uuid='SOME_UID')
def test_func():
pass
self.assertEqual(test_func.__name__, "test_func")
def test_function_doc(self):
"""Test test_func.__doc__ returns its original unwrapped doc string"""
@test_decorators.test_info(uuid='SOME_UID')
def test_func():
"""DOC_STRING"""
pass
self.assertEqual(test_func.__doc__, "DOC_STRING")
def test_function_module(self):
"""Test test_func.__module__ returns its original unwrapped module"""
@test_decorators.test_info(uuid='SOME_UID')
def test_func():
pass
self.assertEqual(test_func.__module__, self.__module__)
class MockTest(base_test.BaseTestClass):
TEST_CASE_LIST = 'test_run_mock_test'
TEST_LOGIC_ATTR = 'test_logic'
@test_decorators.test_info(uuid=TEST_TRACKER_UUID)
def test_run_mock_test(self):
getattr(MockTest, MockTest.TEST_LOGIC_ATTR, None)()
class FakeTest(base_test.BaseTestClass):
"""A fake test class used to test TestTrackerInfoDecoratorFunc."""
def __init__(self):
self.user_params = {
'testtracker_properties': 'tt_prop_a=param_a,'
'tt_prop_b=param_b',
'param_a': 'prop_a_value',
'param_b': 'prop_b_value',
}
self.log_path = '/dummy/path'
self.begin_time = 1000000
@test_decorators.test_tracker_info(uuid='SOME_UID')
def test_case(self):
pass
class TestTrackerInfoDecoratorFuncTests(unittest.TestCase):
@mock.patch('acts.test_decorators.TestTrackerResultsWriter')
def test_write_to_testtracker_no_effort_name_no_ad(self, mock_writer):
"""Should not set the TT Effort name."""
with self.assertRaises(signals.TestPass):
FakeTest().test_case()
testtracker_properties = mock_writer.call_args[0][1]
self.assertEqual(testtracker_properties,
{'tt_prop_a': 'prop_a_value',
'tt_prop_b': 'prop_b_value'})
@mock.patch('acts.test_decorators.TestTrackerResultsWriter')
def test_write_to_testtracker_no_effort_name_has_ad(self, mock_writer):
"""Should set the TT Effort Name using the AndroidDevice."""
fake_test = FakeTest()
fake_test.android_devices = [mock.Mock()]
fake_test.android_devices[0].build_info = {'build_id': 'EFFORT_NAME'}
with self.assertRaises(signals.TestPass):
fake_test.test_case()
testtracker_properties = mock_writer.call_args[0][1]
self.assertEqual(testtracker_properties,
{'tt_prop_a': 'prop_a_value',
'tt_prop_b': 'prop_b_value',
KEY_EFFORT_NAME: 'EFFORT_NAME'})
@mock.patch('acts.test_decorators.TestTrackerResultsWriter')
def test_write_to_testtracker_has_effort_name_has_ad(self, mock_writer):
"""Should set the TT Effort Name using the AndroidDevice."""
fake_test = FakeTest()
fake_test.android_devices = [mock.Mock()]
fake_test.android_devices[0].build_info = {'build_id': 'BAD_EFFORT'}
fake_test.user_params['testtracker_properties'] += (
',' + KEY_EFFORT_NAME + '=param_effort_name')
fake_test.user_params['param_effort_name'] = 'GOOD_EFFORT_NAME'
with self.assertRaises(signals.TestPass):
fake_test.test_case()
testtracker_properties = mock_writer.call_args[0][1]
self.assertEqual(testtracker_properties,
{'tt_prop_a': 'prop_a_value',
'tt_prop_b': 'prop_b_value',
KEY_EFFORT_NAME: 'GOOD_EFFORT_NAME'})
@mock.patch('acts.test_decorators.TestTrackerResultsWriter')
def test_no_testtracker_properties_provided(self, mock_writer):
fake_test = FakeTest()
del fake_test.user_params['testtracker_properties']
with self.assertRaises(signals.TestPass):
fake_test.test_case()
self.assertFalse(mock_writer.called)
@mock.patch('acts.test_decorators.TestTrackerResultsWriter')
def test_needs_base_class_set_to_write_to_testtracker(self, mock_writer):
class DoesntHaveBaseClass(object):
@test_decorators.test_tracker_info(uuid='SOME_UID')
def test_case(self):
pass
with self.assertRaises(signals.TestPass):
DoesntHaveBaseClass().test_case()
self.assertFalse(mock_writer.called)
@mock.patch('acts.test_decorators.TestTrackerResultsWriter')
def test_does_not_write_when_decorated_args_are_missing(self, mock_writer):
@test_decorators.test_tracker_info(uuid='SOME_UID')
def some_decorated_func():
pass
with self.assertRaises(signals.TestPass):
some_decorated_func()
self.assertFalse(mock_writer.called)
class TestDecoratorIntegrationTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.tmp_dir = tempfile.mkdtemp()
cls.MOCK_CONFIG = mobly_config_parser.TestRunConfig()
cls.MOCK_CONFIG.testbed_name = 'SampleTestBed'
cls.MOCK_CONFIG.log_path = cls.tmp_dir
cls.MOCK_TEST_RUN_LIST = [(MockTest.__name__,
[MockTest.TEST_CASE_LIST])]
@classmethod
def tearDownClass(cls):
shutil.rmtree(cls.tmp_dir)
def _run_with_test_logic(self, func):
if hasattr(MockTest, MockTest.TEST_LOGIC_ATTR):
delattr(MockTest, MockTest.TEST_LOGIC_ATTR)
setattr(MockTest, MockTest.TEST_LOGIC_ATTR, func)
self.test_runner = test_runner.TestRunner(self.MOCK_CONFIG,
self.MOCK_TEST_RUN_LIST)
self.test_runner.run(MockTest)
def _validate_results_has_extra(self, result, extra_key, extra_value):
results = self.test_runner.results
self.assertGreaterEqual(len(results.executed), 1,
'Expected at least one executed test.')
record = results.executed[0]
self.assertIsNotNone(record.extras,
'Expected the test record to have extras.')
self.assertEqual(record.extras[extra_key], extra_value)
def test_mock_test_with_raise_pass(self):
self._run_with_test_logic(raise_pass)
self._validate_results_has_extra(self.test_runner.results, UUID_KEY,
TEST_TRACKER_UUID)
def test_mock_test_with_raise_generic(self):
self._run_with_test_logic(raise_generic)
self._validate_results_has_extra(self.test_runner.results, UUID_KEY,
TEST_TRACKER_UUID)
class RepeatedTestTests(unittest.TestCase):
def test_all_error_types_count_toward_failures(self):
def result_selector(results, _):
self.assertIsInstance(results[0], AssertionError)
self.assertIsInstance(results[1], signals.TestFailure)
self.assertIsInstance(results[2], signals.TestError)
self.assertIsInstance(results[3], IndexError)
raise signals.TestPass('Expected failures occurred')
@test_decorators.repeated_test(1, 3, result_selector)
def test_case(_, attempt_number):
if attempt_number == 1:
raise AssertionError()
elif attempt_number == 2:
raise signals.TestFailure('Failed')
elif attempt_number == 3:
raise signals.TestError('Error')
else:
# Note that any Exception that does not fall into another bucket
# is also considered a failure
raise IndexError('Bad index')
with self.assertRaises(signals.TestPass):
test_case(mock.Mock())
def test_passes_stop_repeating_the_test_case(self):
def result_selector(results, _):
self.assertEqual(len(results), 3)
for result in results:
self.assertIsInstance(result, signals.TestPass)
raise signals.TestPass('Expected passes occurred')
@test_decorators.repeated_test(3, 0, result_selector)
def test_case(*_):
raise signals.TestPass('Passed')
with self.assertRaises(signals.TestPass):
test_case(mock.Mock())
def test_abort_signals_are_uncaught(self):
@test_decorators.repeated_test(3, 0)
def test_case(*_):
raise signals.TestAbortClass('Abort All')
with self.assertRaises(signals.TestAbortClass):
test_case(mock.Mock())
def test_keyboard_interrupt_is_uncaught(self):
@test_decorators.repeated_test(3, 0)
def test_case(*_):
raise KeyboardInterrupt()
with self.assertRaises(KeyboardInterrupt):
test_case(mock.Mock())
def test_teardown_and_setup_are_called_between_test_cases(self):
mock_test_class = mock.Mock()
@test_decorators.repeated_test(1, 1)
def test_case(*_):
raise signals.TestFailure('Failed')
with self.assertRaises(signals.TestFailure):
test_case(mock_test_class)
self.assertTrue(mock_test_class.setup_test.called)
self.assertTrue(mock_test_class.teardown_test.called)
def test_result_selector_returned_value_gets_raised(self):
def result_selector(*_):
return signals.TestPass('Expect this to be raised.')
@test_decorators.repeated_test(3, 0, result_selector=result_selector)
def test_case(*_):
raise signals.TestFailure('Result selector ignores this.')
with self.assertRaises(signals.TestPass):
test_case(mock.Mock())
if __name__ == '__main__':
unittest.main()