mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[Build System: build-script] Adds a new cache_utils module to build_swift which replaces the existing module from swift_build_support.
This commit is contained in:
69
utils/build_swift/build_swift/cache_utils.py
Normal file
69
utils/build_swift/build_swift/cache_utils.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# This source file is part of the Swift.org open source project
|
||||
#
|
||||
# Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
|
||||
# Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
#
|
||||
# See https://swift.org/LICENSE.txt for license information
|
||||
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
|
||||
|
||||
"""
|
||||
Cache related utitlity functions and decorators.
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import functools
|
||||
|
||||
|
||||
__all__ = [
|
||||
'cache',
|
||||
'reify',
|
||||
]
|
||||
|
||||
|
||||
def cache(func):
|
||||
"""Decorator that caches result of a function call.
|
||||
|
||||
NOTE: This decorator does not play nice with methods as the created cache
|
||||
is not instance-local, rather it lives in the decorator.
|
||||
NOTE: When running in Python 3.2 or newer this decorator is replaced with
|
||||
the standard `functools.lru_cache` using a maxsize of None.
|
||||
"""
|
||||
|
||||
# Use the standard functools.lru_cache decorator for Python 3.2 and newer.
|
||||
if hasattr(functools, 'lru_cache'):
|
||||
return functools.lru_cache(maxsize=None)(func)
|
||||
|
||||
# Otherwise use a naive caching strategy.
|
||||
_cache = {}
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
key = tuple(args) + tuple(kwargs.items())
|
||||
|
||||
if key not in _cache:
|
||||
result = func(*args, **kwargs)
|
||||
_cache[key] = result
|
||||
return result
|
||||
|
||||
return _cache[key]
|
||||
return wrapper
|
||||
|
||||
|
||||
def reify(func):
|
||||
"""Decorator that replaces the wrapped method with the result after the
|
||||
first call. Used to wrap property-like methods with no arguments.
|
||||
"""
|
||||
|
||||
class wrapper(object):
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
|
||||
result = func(obj)
|
||||
setattr(obj, func.__name__, result)
|
||||
return result
|
||||
|
||||
return functools.update_wrapper(wrapper(), func)
|
||||
121
utils/build_swift/tests/build_swift/test_cache_utils.py
Normal file
121
utils/build_swift/tests/build_swift/test_cache_utils.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# This source file is part of the Swift.org open source project
|
||||
#
|
||||
# Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
|
||||
# Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
#
|
||||
# See https://swift.org/LICENSE.txt for license information
|
||||
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import unittest
|
||||
|
||||
from build_swift import cache_utils
|
||||
|
||||
from .. import utils
|
||||
|
||||
|
||||
try:
|
||||
# Python 3.3
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
mock = None
|
||||
|
||||
|
||||
class _CallCounter(object):
|
||||
"""Callable helper class used to count and return the number of times an
|
||||
instance has been called.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._counter = 0
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
count = self._counter
|
||||
self._counter += 1
|
||||
return count
|
||||
|
||||
|
||||
class TestCache(unittest.TestCase):
|
||||
"""Unit tests for the cache decorator in the cache_utils module.
|
||||
"""
|
||||
|
||||
@utils.requires_module('unittest.mock')
|
||||
@utils.requires_python('3.2') # functools.lru_cache
|
||||
def test_replaced_with_functools_lru_cache_python_3_2(self):
|
||||
with mock.patch('functools.lru_cache') as mock_lru_cache:
|
||||
@cache_utils.cache
|
||||
def func():
|
||||
return None
|
||||
|
||||
mock_lru_cache.assert_called()
|
||||
|
||||
def test_call_with_no_args(self):
|
||||
# Increments the counter once per unique call.
|
||||
counter = _CallCounter()
|
||||
|
||||
@cache_utils.cache
|
||||
def func(*args, **kwargs):
|
||||
return counter(*args, **kwargs)
|
||||
|
||||
self.assertEqual(func(), 0)
|
||||
self.assertEqual(func(), 0)
|
||||
|
||||
def test_call_with_args(self):
|
||||
# Increments the counter once per unique call.
|
||||
counter = _CallCounter()
|
||||
|
||||
@cache_utils.cache
|
||||
def func(*args, **kwargs):
|
||||
return counter(*args, **kwargs)
|
||||
|
||||
self.assertEqual(func(0), 0)
|
||||
self.assertEqual(func(0), 0)
|
||||
|
||||
self.assertEqual(func(1), 1)
|
||||
self.assertEqual(func(1), 1)
|
||||
|
||||
self.assertEqual(func(2), 2)
|
||||
self.assertEqual(func(2), 2)
|
||||
|
||||
def test_call_with_args_and_kwargs(self):
|
||||
# Increments the counter once per unique call.
|
||||
counter = _CallCounter()
|
||||
|
||||
@cache_utils.cache
|
||||
def func(*args, **kwargs):
|
||||
return counter(*args, **kwargs)
|
||||
|
||||
self.assertEqual(func(n=0), 0)
|
||||
self.assertEqual(func(n=0), 0)
|
||||
|
||||
self.assertEqual(func(a=1, b='b'), 1)
|
||||
self.assertEqual(func(a=1, b='b'), 1)
|
||||
|
||||
self.assertEqual(func(0, x=1, y=2.0), 2)
|
||||
self.assertEqual(func(0, x=1, y=2.0), 2)
|
||||
|
||||
|
||||
class TestReify(unittest.TestCase):
|
||||
"""Unit tests for the reify decorator in the cache_utils module.
|
||||
"""
|
||||
|
||||
def test_replaces_attr_after_first_call(self):
|
||||
class Counter(object):
|
||||
def __init__(self):
|
||||
self._counter = 0
|
||||
|
||||
@cache_utils.reify
|
||||
def count(self):
|
||||
count = self._counter
|
||||
self._counter += 1
|
||||
return count
|
||||
|
||||
counter = Counter()
|
||||
|
||||
self.assertEqual(counter.count, 0)
|
||||
self.assertEqual(counter.count, 0)
|
||||
|
||||
# Assert that the count property has been replaced with the constant.
|
||||
self.assertEqual(getattr(counter, 'count'), 0)
|
||||
@@ -15,9 +15,11 @@ import platform
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from six import StringIO
|
||||
from build_swift import cache_utils
|
||||
from build_swift.versions import Version
|
||||
|
||||
from swift_build_support.swift_build_support import cache_util
|
||||
import six
|
||||
from six import StringIO
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -27,6 +29,7 @@ __all__ = [
|
||||
'requires_attr',
|
||||
'requires_module',
|
||||
'requires_platform',
|
||||
'requires_python',
|
||||
|
||||
'BUILD_SCRIPT_IMPL_PATH',
|
||||
'BUILD_SWIFT_PATH',
|
||||
@@ -38,6 +41,8 @@ __all__ = [
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
|
||||
_PYTHON_VERSION = Version(platform.python_version())
|
||||
|
||||
TESTS_PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
BUILD_SWIFT_PATH = os.path.abspath(os.path.join(TESTS_PATH, os.pardir))
|
||||
UTILS_PATH = os.path.abspath(os.path.join(BUILD_SWIFT_PATH, os.pardir))
|
||||
@@ -124,9 +129,10 @@ class redirect_stdout():
|
||||
sys.stderr = self._old_stdout
|
||||
|
||||
|
||||
@cache_util.cached
|
||||
@cache_utils.cache
|
||||
def requires_attr(obj, attr):
|
||||
"""
|
||||
"""Decorator used to skip tests if an object does not have the required
|
||||
attribute.
|
||||
"""
|
||||
|
||||
try:
|
||||
@@ -137,7 +143,7 @@ def requires_attr(obj, attr):
|
||||
attr, obj))
|
||||
|
||||
|
||||
@cache_util.cached
|
||||
@cache_utils.cache
|
||||
def requires_module(fullname):
|
||||
"""Decorator used to skip tests if a module is not imported.
|
||||
"""
|
||||
@@ -148,7 +154,7 @@ def requires_module(fullname):
|
||||
return unittest.skip('Unable to import "{}"'.format(fullname))
|
||||
|
||||
|
||||
@cache_util.cached
|
||||
@cache_utils.cache
|
||||
def requires_platform(name):
|
||||
"""Decorator used to skip tests if not running on the given platform.
|
||||
"""
|
||||
@@ -157,4 +163,20 @@ def requires_platform(name):
|
||||
return lambda func: func
|
||||
|
||||
return unittest.skip(
|
||||
'Required platform "{}"" does not match system'.format(name))
|
||||
'Required platform "{}" does not match system'.format(name))
|
||||
|
||||
|
||||
@cache_utils.cache
|
||||
def requires_python(version):
|
||||
"""Decorator used to skip tests if the running Python version is not
|
||||
greater or equal to the required version.
|
||||
"""
|
||||
|
||||
if isinstance(version, six.string_types):
|
||||
version = Version(version)
|
||||
|
||||
if _PYTHON_VERSION >= version:
|
||||
return lambda func: func
|
||||
|
||||
return unittest.skip(
|
||||
'Requires Python version {} or greater'.format(version))
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# swift_build_support/cache_util.py -----------------------------*- python -*-
|
||||
#
|
||||
# This source file is part of the Swift.org open source project
|
||||
#
|
||||
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
||||
# Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
#
|
||||
# See https://swift.org/LICENSE.txt for license information
|
||||
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
#
|
||||
# ----------------------------------------------------------------------------
|
||||
"""
|
||||
Cache related utilities
|
||||
"""
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
from functools import update_wrapper
|
||||
|
||||
__all__ = [
|
||||
'cached',
|
||||
'reify'
|
||||
]
|
||||
|
||||
|
||||
def cached(func):
|
||||
"""Decorator that caches result of method or function.
|
||||
|
||||
Note: Support method or function.
|
||||
"""
|
||||
cache = {}
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
key = tuple(args) + tuple(kwargs.items())
|
||||
if key not in cache:
|
||||
result = func(*args, **kwargs)
|
||||
cache[key] = result
|
||||
return result
|
||||
else:
|
||||
return cache[key]
|
||||
|
||||
return update_wrapper(wrapper, func)
|
||||
|
||||
|
||||
def reify(func):
|
||||
"""Decorator that replaces the wrapped method with the result after the
|
||||
first call.
|
||||
|
||||
Note: Support method that takes no arguments.
|
||||
"""
|
||||
class Wrapper(object):
|
||||
def __get__(self, obj, objtype=None):
|
||||
if obj is None:
|
||||
return self
|
||||
result = func(obj)
|
||||
setattr(obj, func.__name__, result)
|
||||
return result
|
||||
|
||||
return update_wrapper(Wrapper(), func)
|
||||
@@ -18,10 +18,10 @@ import os.path
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from build_swift.build_swift import cache_utils
|
||||
from build_swift.build_swift.wrappers import xcrun
|
||||
|
||||
from . import product
|
||||
from .. import cache_util
|
||||
from .. import shell
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class NinjaBuilder(product.ProductBuilder):
|
||||
self.args = args
|
||||
self.toolchain = toolchain
|
||||
|
||||
@cache_util.reify
|
||||
@cache_utils.reify
|
||||
def ninja_bin_path(self):
|
||||
return os.path.join(self.build_dir, 'ninja')
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ from __future__ import absolute_import
|
||||
|
||||
import platform
|
||||
|
||||
from build_swift.build_swift import cache_utils
|
||||
from build_swift.build_swift.shell import which
|
||||
from build_swift.build_swift.wrappers import xcrun
|
||||
|
||||
from . import cache_util
|
||||
from . import shell
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ def _register(name, *tool):
|
||||
def _getter(self):
|
||||
return self.find_tool(*tool)
|
||||
_getter.__name__ = name
|
||||
setattr(Toolchain, name, cache_util.reify(_getter))
|
||||
setattr(Toolchain, name, cache_utils.reify(_getter))
|
||||
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
@@ -162,7 +162,7 @@ class FreeBSD(GenericUnix):
|
||||
suffixes = ['38', '37', '36', '35']
|
||||
super(FreeBSD, self).__init__(suffixes)
|
||||
|
||||
@cache_util.reify
|
||||
@cache_utils.reify
|
||||
def _release_date(self):
|
||||
"""Return the release date for FreeBSD operating system on this host.
|
||||
If the release date cannot be ascertained, return None.
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
# tests/test_cache_util.py --------------------------------------*- python -*-
|
||||
#
|
||||
# This source file is part of the Swift.org open source project
|
||||
#
|
||||
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
||||
# Licensed under Apache License v2.0 with Runtime Library Exception
|
||||
#
|
||||
# See https://swift.org/LICENSE.txt for license information
|
||||
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||||
#
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
import unittest
|
||||
|
||||
from swift_build_support import cache_util
|
||||
|
||||
|
||||
my_func_called = 0
|
||||
my_kfunc_called = 0
|
||||
|
||||
|
||||
@cache_util.cached
|
||||
def my_func(arg1, arg2):
|
||||
global my_func_called
|
||||
my_func_called += 1
|
||||
return "my_func_result(%s, %s)" % (arg1, arg2)
|
||||
|
||||
|
||||
@cache_util.cached
|
||||
def my_kfunc(arg1, arg2):
|
||||
global my_kfunc_called
|
||||
my_kfunc_called += 1
|
||||
return "my_kfunc_result(%s, %s)" % (arg1, arg2)
|
||||
|
||||
|
||||
class MyClass(object):
|
||||
def __init__(self, prop=None):
|
||||
self.my_method_called = 0
|
||||
self.my_prop_called = 0
|
||||
self.prop_value = prop
|
||||
|
||||
@cache_util.cached
|
||||
def my_method(self, arg1, arg2):
|
||||
self.my_method_called += 1
|
||||
return "my_meth_result(%s, %s)" % (arg1, arg2)
|
||||
|
||||
@cache_util.reify
|
||||
def my_prop(self):
|
||||
self.my_prop_called += 1
|
||||
return "==%s==" % (self.prop_value)
|
||||
|
||||
|
||||
class CacheUtilTestCase(unittest.TestCase):
|
||||
def test_cached_func(self):
|
||||
self.assertEqual(my_func("foo", 42), "my_func_result(foo, 42)")
|
||||
self.assertEqual(my_func_called, 1)
|
||||
self.assertEqual(my_func("foo", 42), "my_func_result(foo, 42)")
|
||||
self.assertEqual(my_func_called, 1)
|
||||
self.assertEqual(my_func("bar", 42), "my_func_result(bar, 42)")
|
||||
self.assertEqual(my_func_called, 2)
|
||||
self.assertEqual(my_func("foo", 42), "my_func_result(foo, 42)")
|
||||
self.assertEqual(my_func_called, 2)
|
||||
|
||||
def test_cached_kwfunc(self):
|
||||
self.assertEqual(my_kfunc("foo", arg2=42), "my_kfunc_result(foo, 42)")
|
||||
self.assertEqual(my_kfunc_called, 1)
|
||||
self.assertEqual(my_kfunc("foo", arg2=42), "my_kfunc_result(foo, 42)")
|
||||
self.assertEqual(my_kfunc_called, 1)
|
||||
self.assertEqual(my_kfunc("bar", arg2=42), "my_kfunc_result(bar, 42)")
|
||||
self.assertEqual(my_kfunc_called, 2)
|
||||
self.assertEqual(my_kfunc("foo", arg2=42), "my_kfunc_result(foo, 42)")
|
||||
self.assertEqual(my_kfunc_called, 2)
|
||||
|
||||
def test_cached_method(self):
|
||||
obj1 = MyClass()
|
||||
self.assertEqual(obj1.my_method("foo", 42), "my_meth_result(foo, 42)")
|
||||
self.assertEqual(obj1.my_method_called, 1)
|
||||
self.assertEqual(obj1.my_method("foo", 42), "my_meth_result(foo, 42)")
|
||||
self.assertEqual(obj1.my_method_called, 1)
|
||||
self.assertEqual(obj1.my_method("bar", 12), "my_meth_result(bar, 12)")
|
||||
self.assertEqual(obj1.my_method_called, 2)
|
||||
|
||||
# Test for instance independency.
|
||||
obj2 = MyClass()
|
||||
self.assertEqual(obj2.my_method("foo", 42), "my_meth_result(foo, 42)")
|
||||
self.assertEqual(obj2.my_method_called, 1)
|
||||
self.assertEqual(obj1.my_method_called, 2)
|
||||
|
||||
def test_reify(self):
|
||||
obj1 = MyClass(prop='foo')
|
||||
self.assertEqual(obj1.my_prop, '==foo==')
|
||||
self.assertEqual(obj1.my_prop_called, 1)
|
||||
self.assertEqual(obj1.my_prop, '==foo==')
|
||||
self.assertEqual(obj1.my_prop_called, 1)
|
||||
|
||||
# Test for instance independency.
|
||||
obj2 = MyClass(prop='bar')
|
||||
self.assertEqual(obj2.my_prop, '==bar==')
|
||||
self.assertEqual(obj1.my_prop, '==foo==')
|
||||
Reference in New Issue
Block a user