Skip to content

[Build System: build-script] Adds a new cache_utils module to build_swift which replaces the existing module from swift_build_support. #29567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions utils/build_swift/build_swift/cache_utils.py
Original file line number Diff line number Diff line change
@@ -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 changes: 121 additions & 0 deletions utils/build_swift/tests/build_swift/test_cache_utils.py
Original file line number Diff line number Diff line change
@@ -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)
36 changes: 29 additions & 7 deletions utils/build_swift/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
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__ = [
Expand All @@ -27,6 +29,7 @@
'requires_attr',
'requires_module',
'requires_platform',
'requires_python',

'BUILD_SCRIPT_IMPL_PATH',
'BUILD_SWIFT_PATH',
Expand All @@ -38,6 +41,8 @@
# -----------------------------------------------------------------------------
# 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))
Expand Down Expand Up @@ -124,9 +129,10 @@ def __exit__(self, exc_type, exc_value, traceback):
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:
Expand All @@ -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.
"""
Expand All @@ -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.
"""
Expand All @@ -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))
58 changes: 0 additions & 58 deletions utils/swift_build_support/swift_build_support/cache_util.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
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


Expand All @@ -44,7 +44,7 @@ def __init__(self, product_class, args, toolchain, workspace):
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')

Expand Down
6 changes: 3 additions & 3 deletions utils/swift_build_support/swift_build_support/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@

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


Expand All @@ -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':
Expand Down Expand Up @@ -162,7 +162,7 @@ def __init__(self):
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.
Expand Down
Loading