Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(831)

Side by Side Diff: gslib/tests/util.py

Issue 698893003: Update checked in version of gsutil to version 4.6 (Closed) Base URL: http://dart.googlecode.com/svn/third_party/gsutil/
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « gslib/tests/testcase/unit_testcase.py ('k') | gslib/third_party/oauth2_plugin/__init__.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # -*- coding: utf-8 -*-
1 # Copyright 2013 Google Inc. All Rights Reserved. 2 # Copyright 2013 Google Inc. All Rights Reserved.
2 # 3 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # 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 not use this file except in compliance with the License.
5 # You may obtain a copy of the License at 6 # You may obtain a copy of the License at
6 # 7 #
7 # http://www.apache.org/licenses/LICENSE-2.0 8 # http://www.apache.org/licenses/LICENSE-2.0
8 # 9 #
9 # Unless required by applicable law or agreed to in writing, software 10 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and 13 # See the License for the specific language governing permissions and
13 # limitations under the License. 14 # limitations under the License.
14 15
15 import boto 16 from __future__ import absolute_import
17
18 from contextlib import contextmanager
16 import functools 19 import functools
17 import os 20 import os
21 import pkgutil
18 import posixpath 22 import posixpath
19 import pkgutil
20 import re 23 import re
21 import tempfile 24 import tempfile
25 import unittest
22 import urlparse 26 import urlparse
23 27
24 import unittest 28 import boto
29 from boto.provider import Provider
30 import gslib.tests as gslib_tests
31
25 if not hasattr(unittest.TestCase, 'assertIsNone'): 32 if not hasattr(unittest.TestCase, 'assertIsNone'):
26 # external dependency unittest2 required for Python <= 2.6 33 # external dependency unittest2 required for Python <= 2.6
27 import unittest2 as unittest 34 import unittest2 as unittest # pylint: disable=g-import-not-at-top
28
29 from boto.provider import Provider
30 from contextlib import contextmanager
31 import gslib.tests as gslib_tests
32
33 35
34 # Flags for running different types of tests. 36 # Flags for running different types of tests.
35 RUN_INTEGRATION_TESTS = True 37 RUN_INTEGRATION_TESTS = True
36 RUN_UNIT_TESTS = True 38 RUN_UNIT_TESTS = True
39 RUN_S3_TESTS = False
37 40
38 # Whether the tests are running verbose or not. 41 PARALLEL_COMPOSITE_UPLOAD_TEST_CONFIG = '/tmp/.boto.parallel_upload_test_config'
39 VERBOSE_OUTPUT = False
40 42
41 43
42 def _HasS3Credentials(): 44 def _HasS3Credentials():
43 provider = Provider('aws') 45 provider = Provider('aws')
44 if not provider.access_key or not provider.secret_key: 46 if not provider.access_key or not provider.secret_key:
45 return False 47 return False
46 return True 48 return True
47 49
48 HAS_S3_CREDS = _HasS3Credentials() 50 HAS_S3_CREDS = _HasS3Credentials()
49 51
50 52
53 def _HasGSHost():
54 return boto.config.get('Credentials', 'gs_host', None) is not None
55
56 HAS_GS_HOST = _HasGSHost()
57
58
59 def _UsingJSONApi():
60 return boto.config.get('GSUtil', 'prefer_api', 'json').upper() != 'XML'
61
62 USING_JSON_API = _UsingJSONApi()
63
64
51 def _NormalizeURI(uri): 65 def _NormalizeURI(uri):
52 """Normalizes the path component of a URI. 66 """Normalizes the path component of a URI.
53 67
68 Args:
69 uri: URI to normalize.
70
71 Returns:
72 Normalized URI.
73
54 Examples: 74 Examples:
55 gs://foo//bar -> gs://foo/bar 75 gs://foo//bar -> gs://foo/bar
56 gs://foo/./bar -> gs://foo/bar 76 gs://foo/./bar -> gs://foo/bar
57 """ 77 """
58 # Note: we have to do this dance of changing gs:// to file:// because on 78 # Note: we have to do this dance of changing gs:// to file:// because on
59 # Windows, the urlparse function won't work with URL schemes that are not 79 # Windows, the urlparse function won't work with URL schemes that are not
60 # known. urlparse('gs://foo/bar') on Windows turns into: 80 # known. urlparse('gs://foo/bar') on Windows turns into:
61 # scheme='gs', netloc='', path='//foo/bar' 81 # scheme='gs', netloc='', path='//foo/bar'
62 # while on non-Windows platforms, it turns into: 82 # while on non-Windows platforms, it turns into:
63 # scheme='gs', netloc='foo', path='/bar' 83 # scheme='gs', netloc='foo', path='/bar'
64 uri = uri.replace('gs://', 'file://') 84 uri = uri.replace('gs://', 'file://')
65 parsed = list(urlparse.urlparse(uri)) 85 parsed = list(urlparse.urlparse(uri))
66 parsed[2] = posixpath.normpath(parsed[2]) 86 parsed[2] = posixpath.normpath(parsed[2])
67 if parsed[2].startswith('//'): 87 if parsed[2].startswith('//'):
68 # The normpath function doesn't change '//foo' -> '/foo' by design. 88 # The normpath function doesn't change '//foo' -> '/foo' by design.
69 parsed[2] = parsed[2][1:] 89 parsed[2] = parsed[2][1:]
70 unparsed = urlparse.urlunparse(parsed) 90 unparsed = urlparse.urlunparse(parsed)
71 unparsed = unparsed.replace('file://', 'gs://') 91 unparsed = unparsed.replace('file://', 'gs://')
72 return unparsed 92 return unparsed
73 93
74 94
75 def ObjectToURI(obj, *suffixes): 95 def ObjectToURI(obj, *suffixes):
76 """Returns the storage URI string for a given StorageUri or file object. 96 """Returns the storage URI string for a given StorageUri or file object.
77 97
78 Args: 98 Args:
79 obj: The object to get the URI from. Can be a file object, a subclass of 99 obj: The object to get the URI from. Can be a file object, a subclass of
80 boto.storage_uri.StorageURI, or a string. If a string, it is assumed to 100 boto.storage_uri.StorageURI, or a string. If a string, it is assumed to
81 be a local on-disk path. 101 be a local on-disk path.
82 suffixes: Suffixes to append. For example, ObjectToUri(bucketuri, 'foo') 102 *suffixes: Suffixes to append. For example, ObjectToUri(bucketuri, 'foo')
83 would return the URI for a key name 'foo' inside the given bucket. 103 would return the URI for a key name 'foo' inside the given
104 bucket.
105
106 Returns:
107 Storage URI string.
84 """ 108 """
85 if isinstance(obj, file): 109 if isinstance(obj, file):
86 return 'file://%s' % os.path.abspath(os.path.join(obj.name, *suffixes)) 110 return 'file://%s' % os.path.abspath(os.path.join(obj.name, *suffixes))
87 if isinstance(obj, basestring): 111 if isinstance(obj, basestring):
88 return 'file://%s' % os.path.join(obj, *suffixes) 112 return 'file://%s' % os.path.join(obj, *suffixes)
89 uri = obj.uri 113 uri = obj.uri
90 if suffixes: 114 if suffixes:
91 uri = _NormalizeURI('/'.join([uri] + list(suffixes))) 115 uri = _NormalizeURI('/'.join([uri] + list(suffixes)))
92 116
93 # Storage URIs shouldn't contain a trailing slash. 117 # Storage URIs shouldn't contain a trailing slash.
94 if uri.endswith('/'): 118 if uri.endswith('/'):
95 uri = uri[:-1] 119 uri = uri[:-1]
96 return uri 120 return uri
97 121
122 # The mock storage service comes from the Boto library, but it is not
123 # distributed with Boto when installed as a package. To get around this, we
124 # copy the file to gslib/tests/mock_storage_service.py when building the gsutil
125 # package. Try and import from both places here.
126 # pylint: disable=g-import-not-at-top
127 try:
128 from gslib.tests import mock_storage_service
129 except ImportError:
130 try:
131 from boto.tests.integration.s3 import mock_storage_service
132 except ImportError:
133 try:
134 from tests.integration.s3 import mock_storage_service
135 except ImportError:
136 import mock_storage_service
137
138
139 class GSMockConnection(mock_storage_service.MockConnection):
140
141 def __init__(self, *args, **kwargs):
142 kwargs['provider'] = 'gs'
143 super(GSMockConnection, self).__init__(*args, **kwargs)
144
145 mock_connection = GSMockConnection()
146
147
148 class GSMockBucketStorageUri(mock_storage_service.MockBucketStorageUri):
149
150 def connect(self, access_key_id=None, secret_access_key=None):
151 return mock_connection
152
153 def compose(self, components, headers=None):
154 """Dummy implementation to allow parallel uploads with tests."""
155 return self.new_key()
156
157
158 TEST_BOTO_REMOVE_SECTION = 'TestRemoveSection'
159
160
161 def _SetBotoConfig(section, name, value, revert_list):
162 """Sets boto configuration temporarily for testing.
163
164 SetBotoConfigForTest and SetBotoConfigFileForTest should be called by tests
165 instead of this function. Those functions will ensure that the configuration
166 is reverted to its original setting using _RevertBotoConfig.
167
168 Args:
169 section: Boto config section to set
170 name: Boto config name to set
171 value: Value to set
172 revert_list: List for tracking configs to revert.
173 """
174 prev_value = boto.config.get(section, name, None)
175 if not boto.config.has_section(section):
176 revert_list.append((section, TEST_BOTO_REMOVE_SECTION, None))
177 boto.config.add_section(section)
178 revert_list.append((section, name, prev_value))
179 if value is None:
180 boto.config.remove_option(section, name)
181 else:
182 boto.config.set(section, name, value)
183
184
185 def _RevertBotoConfig(revert_list):
186 """Reverts boto config modifications made by _SetBotoConfig.
187
188 Args:
189 revert_list: List of boto config modifications created by calls to
190 _SetBotoConfig.
191 """
192 sections_to_remove = []
193 for section, name, value in revert_list:
194 if value is None:
195 if name == TEST_BOTO_REMOVE_SECTION:
196 sections_to_remove.append(section)
197 else:
198 boto.config.remove_option(section, name)
199 else:
200 boto.config.set(section, name, value)
201 for section in sections_to_remove:
202 boto.config.remove_section(section)
203
204
98 def PerformsFileToObjectUpload(func): 205 def PerformsFileToObjectUpload(func):
99 """Decorator used to indicate that a test performs an upload from a local 206 """Decorator indicating that a test uploads from a local file to an object.
100 file to an object. This forces the test to run once normally, and again 207
101 with a special .boto config file that will ensure that the test follows 208 This forces the test to run once normally, and again with special boto
102 the parallel composite upload code path. 209 config settings that will ensure that the test follows the parallel composite
210 upload code path.
211
212 Args:
213 func: Function to wrap.
214
215 Returns:
216 Wrapped function.
103 """ 217 """
104 @functools.wraps(func) 218 @functools.wraps(func)
105 def wrapper(*args, **kwargs): 219 def Wrapper(*args, **kwargs):
220 # Run the test normally once.
221 func(*args, **kwargs)
222
223 # Try again, forcing parallel composite uploads.
224 with SetBotoConfigForTest([
225 ('GSUtil', 'parallel_composite_upload_threshold', '1'),
226 ('GSUtil', 'check_hashes', 'always')]):
227 func(*args, **kwargs)
228
229 return Wrapper
230
231
232 @contextmanager
233 def SetBotoConfigForTest(boto_config_list):
234 """Sets the input list of boto configs for the duration of a 'with' clause.
235
236 Args:
237 boto_config_list: list of tuples of:
238 (boto config section to set, boto config name to set, value to set)
239
240 Yields:
241 Once after config is set.
242 """
243 revert_configs = []
244 tmp_filename = None
245 try:
106 tmp_fd, tmp_filename = tempfile.mkstemp(prefix='gsutil-temp-cfg') 246 tmp_fd, tmp_filename = tempfile.mkstemp(prefix='gsutil-temp-cfg')
107 os.close(tmp_fd) 247 os.close(tmp_fd)
248 for boto_config in boto_config_list:
249 _SetBotoConfig(boto_config[0], boto_config[1], boto_config[2],
250 revert_configs)
251 with open(tmp_filename, 'w') as tmp_file:
252 boto.config.write(tmp_file)
108 253
109 try: 254 with SetBotoConfigFileForTest(tmp_filename):
110 # Run the test normally once. 255 yield
111 func(*args, **kwargs) 256 finally:
112 257 _RevertBotoConfig(revert_configs)
113 # Try again, forcing parallel composite uploads. 258 if tmp_filename:
114 boto.config.set('GSUtil', 'parallel_composite_upload_threshold', '1')
115 with open(tmp_filename, 'w') as tmp_file:
116 boto.config.write(tmp_file)
117
118 with SetBotoConfigForTest(tmp_filename):
119 func(*args, **kwargs)
120 finally:
121 try: 259 try:
122 os.remove(tmp_filename) 260 os.remove(tmp_filename)
123 except OSError: 261 except OSError:
124 pass 262 pass
125 263
126 return wrapper
127 264
128 @contextmanager 265 @contextmanager
129 def SetBotoConfigForTest(boto_config_path): 266 def SetBotoConfigFileForTest(boto_config_path):
130 """Sets a given file as the boto config file for a single test.""" 267 """Sets a given file as the boto config file for a single test."""
131
132 # Setup for entering "with" block. 268 # Setup for entering "with" block.
133 try: 269 try:
134 old_boto_config_env_variable = os.environ['BOTO_CONFIG'] 270 old_boto_config_env_variable = os.environ['BOTO_CONFIG']
135 boto_config_was_set = True 271 boto_config_was_set = True
136 except KeyError: 272 except KeyError:
137 boto_config_was_set = False 273 boto_config_was_set = False
138 os.environ['BOTO_CONFIG'] = boto_config_path 274 os.environ['BOTO_CONFIG'] = boto_config_path
139 275
140 try: 276 try:
141 yield 277 yield
142 finally: 278 finally:
143 # Teardown for exiting "with" block. 279 # Teardown for exiting "with" block.
144 if boto_config_was_set: 280 if boto_config_was_set:
145 os.environ['BOTO_CONFIG'] = old_boto_config_env_variable 281 os.environ['BOTO_CONFIG'] = old_boto_config_env_variable
146 else: 282 else:
147 os.environ.pop('BOTO_CONFIG', None) 283 os.environ.pop('BOTO_CONFIG', None)
148 284
285
149 def GetTestNames(): 286 def GetTestNames():
150 """Returns a list of the names of the test modules in gslib.tests.""" 287 """Returns a list of the names of the test modules in gslib.tests."""
151 matcher = re.compile(r'^test_(?P<name>.*)$') 288 matcher = re.compile(r'^test_(?P<name>.*)$')
152 names = [] 289 names = []
153 for importer, modname, ispkg in pkgutil.iter_modules(gslib_tests.__path__): 290 for _, modname, _ in pkgutil.iter_modules(gslib_tests.__path__):
154 m = matcher.match(modname) 291 m = matcher.match(modname)
155 if m: 292 if m:
156 names.append(m.group('name')) 293 names.append(m.group('name'))
157 return names 294 return names
OLDNEW
« no previous file with comments | « gslib/tests/testcase/unit_testcase.py ('k') | gslib/third_party/oauth2_plugin/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698