From 1c2985efebd5d3543d39b1fadebd17e9d9d45ba7 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Jul 2017 11:12:44 -0700 Subject: [PATCH 1/5] Add _from_any_pb helper --- core/google/cloud/_helpers.py | 23 +++++++++++++++++++++++ core/tests/unit/test__helpers.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/core/google/cloud/_helpers.py b/core/google/cloud/_helpers.py index 2c2f08dcfb45..72918e064507 100644 --- a/core/google/cloud/_helpers.py +++ b/core/google/cloud/_helpers.py @@ -379,6 +379,29 @@ def _bytes_to_unicode(value): raise ValueError('%r could not be converted to unicode' % (value,)) +def _from_any_pb(pb_type, any_pb): + """Converts an Any protobuf to the specified message type + + Args: + pb_type (type): the type of the message that any_pb stores an instance + of. + any_pb (google.protobuf.any_pb2.Any): the object to be converted. + + Returns: + pb_type: An instance of the pb_type message. + + Raises: + TypeError: if the message could not be converted. + """ + msg = pb_type() + if not any_pb.Unpack(msg): + raise TypeError( + 'Could not convert {} to {}'.format( + any_pb.__class__.__name__, pb_type.__name__)) + + return msg + + def _pb_timestamp_to_datetime(timestamp_pb): """Convert a Timestamp protobuf to a datetime object. diff --git a/core/tests/unit/test__helpers.py b/core/tests/unit/test__helpers.py index fcd47f7535bc..f7ba1b2c109f 100644 --- a/core/tests/unit/test__helpers.py +++ b/core/tests/unit/test__helpers.py @@ -554,6 +554,35 @@ def test_it(self): self.assertEqual(self._call_fut(timestamp), dt_stamp) +class Test__from_any_pb(unittest.TestCase): + + def _call_fut(self, pb_type, any_pb): + from google.cloud._helpers import _from_any_pb + + return _from_any_pb(pb_type, any_pb) + + def test_success(self): + from google.protobuf import any_pb2 + from google.type import date_pb2 + + in_message = date_pb2.Date(year=1990) + in_message_any = any_pb2.Any() + in_message_any.Pack(in_message) + out_message = self._call_fut(date_pb2.Date, in_message_any) + self.assertEqual(in_message, out_message) + + def test_failure(self, ): + from google.protobuf import any_pb2 + from google.type import date_pb2 + from google.type import timeofday_pb2 + + in_message = any_pb2.Any() + in_message.Pack(date_pb2.Date(year=1990)) + + with self.assertRaises(TypeError): + self._call_fut(timeofday_pb2.TimeOfDay, in_message) + + class Test__pb_timestamp_to_rfc3339(unittest.TestCase): def _call_fut(self, timestamp): From 9a30dc97513710a40a5d721079fe0b31017d87de Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Jul 2017 12:19:18 -0700 Subject: [PATCH 2/5] Add future bases --- core/google/cloud/future/__init__.py | 21 +++ core/google/cloud/future/_helpers.py | 39 ++++++ core/google/cloud/future/base.py | 173 ++++++++++++++++++++++++ core/tests/unit/future/__init__.py | 0 core/tests/unit/future/test__helpers.py | 52 +++++++ core/tests/unit/future/test_base.py | 134 ++++++++++++++++++ 6 files changed, 419 insertions(+) create mode 100644 core/google/cloud/future/__init__.py create mode 100644 core/google/cloud/future/_helpers.py create mode 100644 core/google/cloud/future/base.py create mode 100644 core/tests/unit/future/__init__.py create mode 100644 core/tests/unit/future/test__helpers.py create mode 100644 core/tests/unit/future/test_base.py diff --git a/core/google/cloud/future/__init__.py b/core/google/cloud/future/__init__.py new file mode 100644 index 000000000000..e5cf2b20ce7e --- /dev/null +++ b/core/google/cloud/future/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2017, Google Inc. +# +# 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. + +"""Futures for dealing with asynchronous operations.""" + +from google.cloud.future.base import Future + +__all__ = [ + 'Future', +] diff --git a/core/google/cloud/future/_helpers.py b/core/google/cloud/future/_helpers.py new file mode 100644 index 000000000000..933d0b8b2d44 --- /dev/null +++ b/core/google/cloud/future/_helpers.py @@ -0,0 +1,39 @@ +# Copyright 2017, Google Inc. +# +# 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. + +"""Private helpers for futures.""" + +import logging +import threading + + +_LOGGER = logging.getLogger(__name__) + + +def start_daemon_thread(*args, **kwargs): + """Starts a thread and marks it as a daemon thread.""" + thread = threading.Thread(*args, **kwargs) + thread.daemon = True + thread.start() + return thread + + +def safe_invoke_callback(callback, *args, **kwargs): + """Invoke a callback, swallowing and logging any exceptions.""" + # pylint: disable=bare-except + # We intentionally want to swallow all exceptions. + try: + return callback(*args, **kwargs) + except: + _LOGGER.exception('Error while executing Future callback.') diff --git a/core/google/cloud/future/base.py b/core/google/cloud/future/base.py new file mode 100644 index 000000000000..230cc8556a68 --- /dev/null +++ b/core/google/cloud/future/base.py @@ -0,0 +1,173 @@ +# Copyright 2017, Google Inc. +# +# 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. + +"""Abstract and helper bases for Future implementations.""" + +import abc + +import six + +from google.cloud.future import _helpers + + +@six.add_metaclass(abc.ABCMeta) +class Future(object): + # pylint: disable=missing-docstring, invalid-name + # We inherit the interfaces here from concurrent.futures. + + """Future interface. + + This interface is based on :class:`concurrent.futures.Future`. + """ + + @abc.abstractmethod + def cancel(self): # pragma: NO COVER + raise NotImplementedError() + + @abc.abstractmethod + def cancelled(self): # pragma: NO COVER + raise NotImplementedError() + + @abc.abstractmethod + def running(self): # pragma: NO COVER + raise NotImplementedError() + + @abc.abstractmethod + def done(self): # pragma: NO COVER + raise NotImplementedError() + + @abc.abstractmethod + def result(self, timeout=None): # pragma: NO COVER + raise NotImplementedError() + + @abc.abstractmethod + def exception(self, timeout=None): # pragma: NO COVER + raise NotImplementedError() + + @abc.abstractmethod + def add_done_callback(self, fn): # pragma: NO COVER + raise NotImplementedError() + + @abc.abstractmethod + def set_result(self, result): # pragma: NO COVER + raise NotImplementedError() + + @abc.abstractmethod + def set_exception(self, exception): # pragma: NO COVER + raise NotImplementedError() + + +class PollingFuture(Future): + """A Future that needs to poll some service to check its status. + + The private :meth:`_blocking_poll` method should be implemented by + subclasses. + + .. note: Privacy here is intended to prevent the final class from + overexposing, not to prevent subclasses from accessing methods. + """ + def __init__(self): + super(PollingFuture, self).__init__() + self._result = None + self._exception = None + self._result_set = False + """bool: Set to True when the result has been set via set_result or + set_exception.""" + self._polling_thread = None + self._done_callbacks = [] + + @abc.abstractmethod + def _blocking_poll(self, timeout=None): + """Poll and wait for the Future to be resolved. + + Args: + timeout (int): How long to wait for the operation to complete. + If None, wait indefinitely. + """ + raise NotImplementedError() + + def result(self, timeout=None): + """Get the result of the operation, blocking if necessary. + + Args: + timeout (int): How long to wait for the operation to complete. + If None, wait indefinitely. + + Returns: + google.protobuf.Message: The Operation's result. + + Raises: + google.gax.GaxError: If the operation errors or if the timeout is + reached before the operation completes. + """ + self._blocking_poll() + + if self._exception is not None: + # pylint: disable=raising-bad-type + # Pylint doesn't recognize that this is valid in this case. + raise self._exception + + return self._result + + def exception(self, timeout=None): + """Get the exception from the operation, blocking if necessary. + + Args: + timeout (int): How long to wait for the operation to complete. + If None, wait indefinitely. + + Returns: + Optional[google.gax.GaxError]: The operation's error. + """ + self._blocking_poll() + return self._exception + + def add_done_callback(self, fn): + """Add a callback to be executed when the operation is complete. + + If the operation is not already complete, this will start a helper + thread to poll for the status of the operation in the background. + + Args: + fn (Callable[Future]): The callback to execute when the operation + is complete. + """ + if self._result_set: + _helpers.safe_invoke_callback(fn, self) + return + + self._done_callbacks.append(fn) + + if self._polling_thread is None: + # The polling thread will exit on its own as soon as the operation + # is done. + self._polling_thread = _helpers.start_daemon_thread( + target=self._blocking_poll) + + def _invoke_callbacks(self, *args, **kwargs): + """Invoke all done callbacks.""" + for callback in self._done_callbacks: + _helpers.safe_invoke_callback(callback, *args, **kwargs) + + def set_result(self, result): + """Set the Future's result.""" + self._result = result + self._result_set = True + self._invoke_callbacks(self) + + def set_exception(self, exception): + """Set the Future's exception.""" + self._exception = exception + self._result_set = True + self._invoke_callbacks(self) diff --git a/core/tests/unit/future/__init__.py b/core/tests/unit/future/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/core/tests/unit/future/test__helpers.py b/core/tests/unit/future/test__helpers.py new file mode 100644 index 000000000000..e56fbb5706b1 --- /dev/null +++ b/core/tests/unit/future/test__helpers.py @@ -0,0 +1,52 @@ +# Copyright 2017, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import mock + +from google.cloud.future import _helpers + + +@mock.patch('threading.Thread', autospec=True) +def test_start_deamon_thread(unused_thread): + deamon_thread = _helpers.start_daemon_thread(target=mock.sentinel.target) + assert deamon_thread.daemon is True + + +def test_safe_invoke_callback(): + callback = mock.Mock(spec=['__call__'], return_value=42) + result = _helpers.safe_invoke_callback(callback, 'a', b='c') + assert result == 42 + callback.assert_called_once_with('a', b='c') + + +def test_safe_invoke_callback_exception(): + callback = mock.Mock(spec=['__call__'], side_effect=ValueError()) + result = _helpers.safe_invoke_callback(callback, 'a', b='c') + assert result is None + callback.assert_called_once_with('a', b='c') diff --git a/core/tests/unit/future/test_base.py b/core/tests/unit/future/test_base.py new file mode 100644 index 000000000000..11ca52711a2f --- /dev/null +++ b/core/tests/unit/future/test_base.py @@ -0,0 +1,134 @@ +# Copyright 2017, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import mock +import pytest + +from google.cloud.future import base + + +class PollingFutureImpl(base.PollingFuture): + def _blocking_poll(self, timeout=None): # pragma: NO COVER + pass + + def cancel(self): + return True + + def cancelled(self): + return False + + def done(self): + return False + + def running(self): + return True + + +def test_polling_future_constructor(): + future = PollingFutureImpl() + assert not future.done() + assert not future.cancelled() + assert future.running() + assert future.cancel() + + +def test_set_result(): + future = PollingFutureImpl() + callback = mock.Mock() + + future.set_result(1) + + assert future.result() == 1 + future.add_done_callback(callback) + callback.assert_called_once_with(future) + + +def test_set_exception(): + future = PollingFutureImpl() + exception = ValueError('meep') + + future.set_exception(exception) + + assert future.exception() == exception + with pytest.raises(ValueError): + future.result() + + callback = mock.Mock() + future.add_done_callback(callback) + callback.assert_called_once_with(future) + + +def test_invoke_callback_exception(): + future = PollingFutureImplWithPoll() + future.set_result(42) + + # This should not raise, despite the callback causing an exception. + callback = mock.Mock(side_effect=ValueError) + future.add_done_callback(callback) + callback.assert_called_once_with(future) + + +class PollingFutureImplWithPoll(PollingFutureImpl): + def __init__(self): + super(PollingFutureImplWithPoll, self).__init__() + self.poll_count = 0 + + def _blocking_poll(self, timeout=None): + if self._result_set: + return + + self.poll_count += 1 + self.set_result(42) + + +@mock.patch('time.sleep') +def test_result_with_polling(unusued_sleep): + future = PollingFutureImplWithPoll() + + result = future.result() + + assert result == 42 + assert future.poll_count == 1 + # Repeated calls should not cause additional polling + assert future.result() == result + assert future.poll_count == 1 + + +@mock.patch('time.sleep') +def test_callback_background_thread(unused_sleep): + future = PollingFutureImplWithPoll() + callback = mock.Mock() + + future.add_done_callback(callback) + + assert future._polling_thread is not None + + future._polling_thread.join() + + callback.assert_called_once_with(future) From 7b6bab45846508520918d16d035169d5b453217d Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Jul 2017 12:47:06 -0700 Subject: [PATCH 3/5] Fix coverage --- core/google/cloud/future/base.py | 2 +- core/tests/unit/future/test_base.py | 34 +++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/core/google/cloud/future/base.py b/core/google/cloud/future/base.py index 230cc8556a68..14a99311fd9a 100644 --- a/core/google/cloud/future/base.py +++ b/core/google/cloud/future/base.py @@ -88,7 +88,7 @@ def __init__(self): self._done_callbacks = [] @abc.abstractmethod - def _blocking_poll(self, timeout=None): + def _blocking_poll(self, timeout=None): # pragma: NO COVER """Poll and wait for the Future to be resolved. Args: diff --git a/core/tests/unit/future/test_base.py b/core/tests/unit/future/test_base.py index 11ca52711a2f..b24a7115d703 100644 --- a/core/tests/unit/future/test_base.py +++ b/core/tests/unit/future/test_base.py @@ -27,6 +27,8 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import threading + import mock import pytest @@ -98,19 +100,21 @@ class PollingFutureImplWithPoll(PollingFutureImpl): def __init__(self): super(PollingFutureImplWithPoll, self).__init__() self.poll_count = 0 + self.event = threading.Event() def _blocking_poll(self, timeout=None): if self._result_set: return self.poll_count += 1 + self.event.wait() self.set_result(42) -@mock.patch('time.sleep') -def test_result_with_polling(unusued_sleep): +def test_result_with_polling(): future = PollingFutureImplWithPoll() + future.event.set() result = future.result() assert result == 42 @@ -120,15 +124,37 @@ def test_result_with_polling(unusued_sleep): assert future.poll_count == 1 -@mock.patch('time.sleep') -def test_callback_background_thread(unused_sleep): +def test_callback_background_thread(): future = PollingFutureImplWithPoll() callback = mock.Mock() future.add_done_callback(callback) assert future._polling_thread is not None + assert future.poll_count == 1 + + future.event.set() + future._polling_thread.join() + + callback.assert_called_once_with(future) + +def test_double_callback_background_thread(): + future = PollingFutureImplWithPoll() + callback = mock.Mock() + callback2 = mock.Mock() + + future.add_done_callback(callback) + current_thread = future._polling_thread + assert current_thread is not None + + # only one polling thread should be created. + future.add_done_callback(callback2) + assert future._polling_thread is current_thread + + future.event.set() future._polling_thread.join() + assert future.poll_count == 1 callback.assert_called_once_with(future) + callback2.assert_called_once_with(future) From bc47d726ccab166041c2ba0af0bbe6c5b714e0ec Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Jul 2017 12:54:00 -0700 Subject: [PATCH 4/5] Address review comments --- core/google/cloud/future/base.py | 4 ++- core/tests/unit/future/test__helpers.py | 33 +++++++------------------ core/tests/unit/future/test_base.py | 33 +++++++------------------ 3 files changed, 21 insertions(+), 49 deletions(-) diff --git a/core/google/cloud/future/base.py b/core/google/cloud/future/base.py index 14a99311fd9a..0948a81e87c6 100644 --- a/core/google/cloud/future/base.py +++ b/core/google/cloud/future/base.py @@ -23,7 +23,7 @@ @six.add_metaclass(abc.ABCMeta) class Future(object): - # pylint: disable=missing-docstring, invalid-name + # pylint: disable=missing-docstring # We inherit the interfaces here from concurrent.futures. """Future interface. @@ -57,6 +57,7 @@ def exception(self, timeout=None): # pragma: NO COVER @abc.abstractmethod def add_done_callback(self, fn): # pragma: NO COVER + # pylint: disable=invalid-name raise NotImplementedError() @abc.abstractmethod @@ -95,6 +96,7 @@ def _blocking_poll(self, timeout=None): # pragma: NO COVER timeout (int): How long to wait for the operation to complete. If None, wait indefinitely. """ + # pylint: disable=missing-raises raise NotImplementedError() def result(self, timeout=None): diff --git a/core/tests/unit/future/test__helpers.py b/core/tests/unit/future/test__helpers.py index e56fbb5706b1..cbca5ba4d4df 100644 --- a/core/tests/unit/future/test__helpers.py +++ b/core/tests/unit/future/test__helpers.py @@ -1,31 +1,16 @@ # Copyright 2017, Google Inc. -# All rights reserved. # -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: +# 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 # -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. +# http://www.apache.org/licenses/LICENSE-2.0 # -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# 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 diff --git a/core/tests/unit/future/test_base.py b/core/tests/unit/future/test_base.py index b24a7115d703..f10c10b24fb4 100644 --- a/core/tests/unit/future/test_base.py +++ b/core/tests/unit/future/test_base.py @@ -1,31 +1,16 @@ # Copyright 2017, Google Inc. -# All rights reserved. # -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: +# 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 # -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. +# http://www.apache.org/licenses/LICENSE-2.0 # -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# 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 threading From 89e38b9cc4ade13a6ac1f2668e660a466d38d3c2 Mon Sep 17 00:00:00 2001 From: Jon Wayne Parrott Date: Mon, 17 Jul 2017 13:56:42 -0700 Subject: [PATCH 5/5] Skip notimplementederror in covaragerc --- core/.coveragerc | 3 +++ core/google/cloud/future/base.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/.coveragerc b/core/.coveragerc index 9d89b1db5666..ce75f605a508 100644 --- a/core/.coveragerc +++ b/core/.coveragerc @@ -13,3 +13,6 @@ exclude_lines = pragma: NO COVER # Ignore debug-only repr def __repr__ + # Ignore abstract methods + raise NotImplementedError + raise NotImplementedError() diff --git a/core/google/cloud/future/base.py b/core/google/cloud/future/base.py index 0948a81e87c6..928269506b65 100644 --- a/core/google/cloud/future/base.py +++ b/core/google/cloud/future/base.py @@ -32,40 +32,40 @@ class Future(object): """ @abc.abstractmethod - def cancel(self): # pragma: NO COVER + def cancel(self): raise NotImplementedError() @abc.abstractmethod - def cancelled(self): # pragma: NO COVER + def cancelled(self): raise NotImplementedError() @abc.abstractmethod - def running(self): # pragma: NO COVER + def running(self): raise NotImplementedError() @abc.abstractmethod - def done(self): # pragma: NO COVER + def done(self): raise NotImplementedError() @abc.abstractmethod - def result(self, timeout=None): # pragma: NO COVER + def result(self, timeout=None): raise NotImplementedError() @abc.abstractmethod - def exception(self, timeout=None): # pragma: NO COVER + def exception(self, timeout=None): raise NotImplementedError() @abc.abstractmethod - def add_done_callback(self, fn): # pragma: NO COVER + def add_done_callback(self, fn): # pylint: disable=invalid-name raise NotImplementedError() @abc.abstractmethod - def set_result(self, result): # pragma: NO COVER + def set_result(self, result): raise NotImplementedError() @abc.abstractmethod - def set_exception(self, exception): # pragma: NO COVER + def set_exception(self, exception): raise NotImplementedError() @@ -89,7 +89,7 @@ def __init__(self): self._done_callbacks = [] @abc.abstractmethod - def _blocking_poll(self, timeout=None): # pragma: NO COVER + def _blocking_poll(self, timeout=None): """Poll and wait for the Future to be resolved. Args: