blob: 499d5e90240f9d9222230a5bada600e421df485d [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2018 - The Android Open Source Project
#
# 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.
from __future__ import absolute_import
import os
import sys
# A temporary hack to prevent tests/libs/logging from being selected as the
# python default logging module.
sys.path[0] = os.path.join(sys.path[0], '../')
import logging
import mock
import shutil
import tempfile
import unittest
from acts import base_test
from acts.libs import version_selector
from acts.test_decorators import test_tracker_info
from mobly.config_parser import TestRunConfig
def versioning_decorator(min_sdk, max_sdk):
return version_selector.set_version(lambda ret, *_, **__: ret, min_sdk,
max_sdk)
def versioning_decorator2(min_sdk, max_sdk):
return version_selector.set_version(lambda ret, *_, **__: ret, min_sdk,
max_sdk)
def test_versioning(min_sdk, max_sdk):
return version_selector.set_version(lambda *_, **__: 1, min_sdk, max_sdk)
@versioning_decorator(1, 10)
def versioned_func(arg1, arg2):
return 'function 1', arg1, arg2
@versioning_decorator(11, 11)
def versioned_func(arg1, arg2):
return 'function 2', arg1, arg2
@versioning_decorator(12, 20)
def versioned_func(arg1, arg2):
return 'function 3', arg1, arg2
@versioning_decorator(1, 20)
def versioned_func_with_kwargs(_, asdf='jkl'):
return asdf
def class_versioning_decorator(min_sdk, max_sdk):
return version_selector.set_version(lambda _, ret, *__, **___: ret,
min_sdk, max_sdk)
class VersionedClass(object):
@classmethod
@class_versioning_decorator(1, 99999999)
def class_func(cls, arg1):
return cls, arg1
@staticmethod
@versioning_decorator(1, 99999999)
def static_func(arg1):
return arg1
@class_versioning_decorator(1, 99999999)
def instance_func(self, arg1):
return self, arg1
class VersionedTestClass(base_test.BaseTestClass):
@mock.patch('mobly.utils.create_dir')
def __init__(self, configs, _):
super().__init__(configs)
@test_tracker_info('UUID_1')
@test_versioning(1, 1)
def test_1(self):
pass
@test_versioning(1, 1)
@test_tracker_info('UUID_2')
def test_2(self):
pass
class VersionSelectorIntegrationTest(unittest.TestCase):
"""Tests the acts.libs.version_selector module."""
@classmethod
def setUpClass(cls):
cls.tmp_dir = tempfile.mkdtemp()
@classmethod
def tearDownClass(cls):
shutil.rmtree(cls.tmp_dir)
def test_versioned_test_class_calls_both_functions(self):
"""Tests that VersionedTestClass (above) can be called with
test_tracker_info."""
test_run_config = TestRunConfig()
test_run_config.log_path = ''
test_run_config.summary_writer = mock.MagicMock()
test_run_config.log_path = self.tmp_dir
test_class = VersionedTestClass(test_run_config)
test_class.run(['test_1', 'test_2'])
self.assertIn('Executed 2', test_class.results.summary_str(),
'One or more of the test cases did not execute.')
self.assertEqual(
'UUID_1',
test_class.results.executed[0].extras['test_tracker_uuid'],
'The test_tracker_uuid was not found for test_1.')
self.assertEqual(
'UUID_2',
test_class.results.executed[1].extras['test_tracker_uuid'],
'The test_tracker_uuid was not found for test_2.')
def test_raises_syntax_error_if_decorated_with_staticmethod_first(self):
try:
class SomeClass(object):
@versioning_decorator(1, 1)
@staticmethod
def test_1():
pass
except SyntaxError:
pass
else:
self.fail('Placing the @staticmethod decorator after the '
'versioning decorator should cause a SyntaxError.')
def test_raises_syntax_error_if_decorated_with_classmethod_first(self):
try:
class SomeClass(object):
@versioning_decorator(1, 1)
@classmethod
def test_1(cls):
pass
except SyntaxError:
pass
else:
self.fail('Placing the @classmethod decorator after the '
'versioning decorator should cause a SyntaxError.')
def test_overriding_an_undecorated_func_raises_a_syntax_error(self):
try:
class SomeClass(object):
def test_1(self):
pass
@versioning_decorator(1, 1)
def test_1(self):
pass
except SyntaxError:
pass
else:
self.fail('Overwriting a function that already exists without a '
'versioning decorator should raise a SyntaxError.')
def test_func_decorated_with_2_different_versioning_decorators_causes_error(
self):
try:
class SomeClass(object):
@versioning_decorator(1, 1)
def test_1(self):
pass
@versioning_decorator2(2, 2)
def test_1(self):
pass
except SyntaxError:
pass
else:
self.fail('Using two different versioning decorators to version a '
'single function should raise a SyntaxError.')
def test_func_decorated_with_overlapping_ranges_causes_value_error(self):
try:
class SomeClass(object):
@versioning_decorator(1, 2)
def test_1(self):
pass
@versioning_decorator(2, 2)
def test_1(self):
pass
except ValueError:
pass
else:
self.fail('Decorated functions with overlapping version ranges '
'should raise a ValueError.')
def test_func_decorated_with_min_gt_max_causes_value_error(self):
try:
class SomeClass(object):
@versioning_decorator(2, 1)
def test_1(self):
pass
except ValueError:
pass
else:
self.fail(
'If the min_version level is higher than the max_version '
'level, a ValueError should be raised.')
def test_calling_versioned_func_on_min_version_level_is_inclusive(self):
"""Tests that calling some versioned function with the minimum version
level of the decorated function will call that function."""
ret = versioned_func(1, 'some_value')
self.assertEqual(
ret, ('function 1', 1, 'some_value'),
'Calling versioned_func(1, ...) did not return the '
'versioned function for the correct range.')
def test_calling_versioned_func_on_middle_level_works(self):
"""Tests that calling some versioned function a version value within the
range of the decorated function will call that function."""
ret = versioned_func(16, 'some_value')
self.assertEqual(
ret, ('function 3', 16, 'some_value'),
'Calling versioned_func(16, ...) did not return the '
'versioned function for the correct range.')
def test_calling_versioned_func_on_max_version_level_is_inclusive(self):
"""Tests that calling some versioned function with the maximum version
level of the decorated function will call that function."""
ret = versioned_func(10, 'some_value')
self.assertEqual(
ret, ('function 1', 10, 'some_value'),
'Calling versioned_func(10, ...) did not return the '
'versioned function for the correct range.')
def test_calling_versioned_func_on_min_equals_max_level_works(self):
"""Tests that calling some versioned function with the maximum version
level of the decorated function will call that function."""
ret = versioned_func(11, 'some_value')
self.assertEqual(
ret, ('function 2', 11, 'some_value'),
'Calling versioned_func(10, ...) did not return the '
'versioned function for the correct range.')
def test_sending_kwargs_through_decorated_functions_works(self):
"""Tests that calling some versioned function with the maximum version
level of the decorated function will call that function."""
ret = versioned_func_with_kwargs(1, asdf='some_value')
self.assertEqual(
ret, 'some_value',
'Calling versioned_func_with_kwargs(1, ...) did not'
'return the kwarg value properly.')
def test_kwargs_can_default_through_decorated_functions(self):
"""Tests that calling some versioned function with the maximum version
level of the decorated function will call that function."""
ret = versioned_func_with_kwargs(1)
self.assertEqual(
ret, 'jkl', 'Calling versioned_func_with_kwargs(1) did not'
'return the default kwarg value properly.')
def test_staticmethod_can_be_called_properly(self):
"""Tests that decorating a staticmethod will properly send the arguments
in the correct order.
i.e., we want to make sure self or cls do not get sent as the first
argument to the decorated staticmethod.
"""
versioned_class = VersionedClass()
ret = versioned_class.static_func(123456)
self.assertEqual(
ret, 123456, 'The first argument was not set properly for calling '
'a staticmethod.')
def test_instance_method_can_be_called_properly(self):
"""Tests that decorating a method will properly send the arguments
in the correct order.
i.e., we want to make sure self is the first argument returned.
"""
versioned_class = VersionedClass()
ret = versioned_class.instance_func(123456)
self.assertEqual(
ret, (versioned_class, 123456),
'The arguments were not set properly for an instance '
'method.')
def test_classmethod_can_be_called_properly(self):
"""Tests that decorating a classmethod will properly send the arguments
in the correct order.
i.e., we want to make sure cls is the first argument returned.
"""
versioned_class = VersionedClass()
ret = versioned_class.class_func(123456)
self.assertEqual(
ret, (VersionedClass, 123456),
'The arguments were not set properly for a '
'classmethod.')
if __name__ == '__main__':
unittest.main()