Skip to content

Commit 2eeff37

Browse files
authored
Raise TypeError on bare decorator factory usage, fix existing bare usages, and add regression tests (aws#10131)
1 parent 7309c49 commit 2eeff37

File tree

6 files changed

+142
-9
lines changed

6 files changed

+142
-9
lines changed

tests/functional/s3/test_mv_command.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ def test_mv_works_if_outpost_access_point_arn_resolves_to_different_bucket(
835835
self.assertEqual(self.operations_called[2][0].name, 'CopyObject')
836836
self.assertEqual(self.operations_called[3][0].name, 'DeleteObject')
837837

838-
@requires_crt
838+
@requires_crt()
839839
def test_mv_works_if_mrap_arn_resolves_to_different_bucket(self):
840840
cmdline = (
841841
f"{self.prefix} s3://bucket/key "
@@ -911,7 +911,7 @@ def test_skips_validation_if_keys_are_different_outpost_alias(self):
911911
)
912912
self.assert_runs_mv_without_validation(cmdline)
913913

914-
@requires_crt
914+
@requires_crt()
915915
def test_skips_validation_if_keys_are_different_mrap_arn(self):
916916
cmdline = (
917917
f"{self.prefix} s3://bucket/key "

tests/functional/s3transfer/test_crt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def on_done(self, future, **kwargs):
6262
self.on_done_future = future
6363

6464

65-
@requires_crt
65+
@requires_crt()
6666
class TestCRTTransferManager(unittest.TestCase):
6767
def setUp(self):
6868
self.region = 'us-west-2'

tests/integration/s3transfer/test_crt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def on_done(self, **kwargs):
4949
self.on_done_called = True
5050

5151

52-
@requires_crt
52+
@requires_crt()
5353
class TestCRTS3Transfers(BaseTransferManagerIntegTest):
5454
"""Tests for the high level s3transfer based on CRT implementation."""
5555

tests/unit/s3transfer/test_crt.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def test_multiple_acquires_return_same_lock(self, mock_crt_process_lock):
8989
mock_crt_process_lock.return_value.acquire.assert_called_once_with()
9090

9191

92-
@requires_crt
92+
@requires_crt()
9393
class TestBotocoreCRTRequestSerializer(unittest.TestCase):
9494
def setUp(self):
9595
self.region = 'us-west-2'
@@ -291,7 +291,7 @@ def test_to_crt_credentials_provider(self, botocore_credentials):
291291
self.assert_crt_credentials(crt_credentials)
292292

293293

294-
@requires_crt
294+
@requires_crt()
295295
class TestCRTTransferFuture(unittest.TestCase):
296296
def setUp(self):
297297
self.mock_s3_request = mock.Mock(awscrt.s3.S3RequestType)
@@ -320,7 +320,7 @@ def test_set_exception_can_override_previous_exception(self):
320320
self.future.result()
321321

322322

323-
@requires_crt
323+
@requires_crt()
324324
class TestOnBodyFileObjWriter(unittest.TestCase):
325325
def test_call(self):
326326
fileobj = io.BytesIO()

tests/unit/test_decorators.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License").
4+
# You may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
from unittest import mock
15+
16+
import pytest
17+
18+
from tests.utils.s3transfer import (
19+
requires_crt,
20+
skip_if_using_serial_implementation,
21+
skip_if_windows,
22+
)
23+
24+
25+
class TestSkipIfWindows:
26+
def test_bare_skip_if_windows_fails_immediately(self):
27+
with pytest.raises(TypeError):
28+
29+
@skip_if_windows
30+
def my_test():
31+
pass
32+
33+
def test_skip_if_windows_skips_on_windows(self):
34+
with mock.patch('tests.utils.s3transfer.platform') as mock_platform:
35+
mock_platform.system.return_value = 'Windows'
36+
37+
@skip_if_windows('Not supported on Windows')
38+
def my_test():
39+
assert False
40+
41+
assert getattr(my_test, '__unittest_skip__', False) is True
42+
43+
def test_skip_if_windows_runs_on_non_windows(self):
44+
with mock.patch('tests.utils.s3transfer.platform') as mock_platform:
45+
mock_platform.system.return_value = 'Linux'
46+
47+
@skip_if_windows('Not supported on Windows')
48+
def my_test():
49+
pass
50+
51+
assert getattr(my_test, '__unittest_skip__', False) is False
52+
53+
54+
class TestSkipIfUsingSerialImplementation:
55+
def test_bare_skip_if_using_serial_implementation_fails_immediately(self):
56+
with pytest.raises(TypeError):
57+
58+
@skip_if_using_serial_implementation
59+
def my_test():
60+
pass
61+
62+
def test_skip_if_using_serial_implementation_skips_when_serial(self):
63+
with mock.patch(
64+
'tests.utils.s3transfer.is_serial_implementation',
65+
return_value=True,
66+
):
67+
68+
@skip_if_using_serial_implementation(
69+
'Not supported in serial mode'
70+
)
71+
def my_test():
72+
assert False
73+
74+
assert getattr(my_test, '__unittest_skip__', False) is True
75+
76+
def test_skip_if_using_serial_implementation_runs_when_not_serial(self):
77+
with mock.patch(
78+
'tests.utils.s3transfer.is_serial_implementation',
79+
return_value=False,
80+
):
81+
82+
@skip_if_using_serial_implementation(
83+
'Not supported in serial mode'
84+
)
85+
def my_test():
86+
pass
87+
88+
assert getattr(my_test, '__unittest_skip__', False) is False
89+
90+
91+
class TestRequiresCrt:
92+
def test_bare_requires_crt_fails_immediately(self):
93+
with pytest.raises(TypeError):
94+
95+
@requires_crt
96+
def my_test():
97+
pass
98+
99+
def test_requires_crt_skips_when_no_crt(self):
100+
with mock.patch('tests.utils.s3transfer.HAS_CRT', False):
101+
102+
@requires_crt()
103+
def my_test():
104+
assert False
105+
106+
assert getattr(my_test, '__unittest_skip__', False) is True
107+
108+
def test_requires_crt_runs_when_crt_available(self):
109+
with mock.patch('tests.utils.s3transfer.HAS_CRT', True):
110+
111+
@requires_crt()
112+
def my_test():
113+
pass
114+
115+
assert getattr(my_test, '__unittest_skip__', False) is False

tests/utils/s3transfer/__init__.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ def test_some_non_windows_stuff(self):
9999
self.assertEqual(...)
100100
101101
"""
102+
if callable(reason):
103+
raise TypeError(
104+
"Use @skip_if_windows('reason') with parentheses, "
105+
"not bare @skip_if_windows"
106+
)
102107

103108
def decorator(func):
104109
return unittest.skipIf(
@@ -110,17 +115,30 @@ def decorator(func):
110115

111116
def skip_if_using_serial_implementation(reason):
112117
"""Decorator to skip tests when running as the serial implementation"""
118+
if callable(reason):
119+
raise TypeError(
120+
"Use @skip_if_using_serial_implementation('reason') with parentheses, "
121+
"not bare @skip_if_using_serial_implementation"
122+
)
113123

114124
def decorator(func):
115125
return unittest.skipIf(is_serial_implementation(), reason)(func)
116126

117127
return decorator
118128

119129

120-
def requires_crt(cls, reason=None):
130+
def requires_crt(reason=None):
131+
if callable(reason):
132+
raise TypeError(
133+
"Use @requires_crt() with parentheses, not bare @requires_crt"
134+
)
121135
if reason is None:
122136
reason = "Test requires awscrt to be installed."
123-
return unittest.skipIf(not HAS_CRT, reason)(cls)
137+
138+
def decorator(func):
139+
return unittest.skipIf(not HAS_CRT, reason)(func)
140+
141+
return decorator
124142

125143

126144
class StreamWithError:

0 commit comments

Comments
 (0)