OLD | NEW |
| (Empty) |
1 # -*- coding: utf-8 -*- | |
2 # Copyright 2013 Google Inc. All Rights Reserved. | |
3 # | |
4 # Licensed under the Apache License, Version 2.0 (the "License"); | |
5 # you may not use this file except in compliance with the License. | |
6 # You may obtain a copy of the License at | |
7 # | |
8 # http://www.apache.org/licenses/LICENSE-2.0 | |
9 # | |
10 # Unless required by applicable law or agreed to in writing, software | |
11 # distributed under the License is distributed on an "AS IS" BASIS, | |
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 # See the License for the specific language governing permissions and | |
14 # limitations under the License. | |
15 """Contains gsutil base unit test case class.""" | |
16 | |
17 from __future__ import absolute_import | |
18 | |
19 import logging | |
20 import os | |
21 import sys | |
22 import tempfile | |
23 | |
24 import boto | |
25 from gslib import wildcard_iterator | |
26 from gslib.boto_translation import BotoTranslation | |
27 from gslib.cloud_api_delegator import CloudApiDelegator | |
28 from gslib.command_runner import CommandRunner | |
29 from gslib.cs_api_map import ApiMapConstants | |
30 from gslib.cs_api_map import ApiSelector | |
31 from gslib.tests.mock_logging_handler import MockLoggingHandler | |
32 from gslib.tests.testcase import base | |
33 import gslib.tests.util as util | |
34 from gslib.tests.util import unittest | |
35 from gslib.tests.util import WorkingDirectory | |
36 from gslib.util import GsutilStreamHandler | |
37 | |
38 | |
39 class GsutilApiUnitTestClassMapFactory(object): | |
40 """Class map factory for use in unit tests. | |
41 | |
42 BotoTranslation is used for all cases so that GSMockBucketStorageUri can | |
43 be used to communicate with the mock XML service. | |
44 """ | |
45 | |
46 @classmethod | |
47 def GetClassMap(cls): | |
48 """Returns a class map for use in unit tests.""" | |
49 gs_class_map = { | |
50 ApiSelector.XML: BotoTranslation, | |
51 ApiSelector.JSON: BotoTranslation | |
52 } | |
53 s3_class_map = { | |
54 ApiSelector.XML: BotoTranslation | |
55 } | |
56 class_map = { | |
57 'gs': gs_class_map, | |
58 's3': s3_class_map | |
59 } | |
60 return class_map | |
61 | |
62 | |
63 @unittest.skipUnless(util.RUN_UNIT_TESTS, | |
64 'Not running integration tests.') | |
65 class GsUtilUnitTestCase(base.GsUtilTestCase): | |
66 """Base class for gsutil unit tests.""" | |
67 | |
68 @classmethod | |
69 def setUpClass(cls): | |
70 base.GsUtilTestCase.setUpClass() | |
71 cls.mock_bucket_storage_uri = util.GSMockBucketStorageUri | |
72 cls.mock_gsutil_api_class_map_factory = GsutilApiUnitTestClassMapFactory | |
73 cls.logger = logging.getLogger() | |
74 cls.command_runner = CommandRunner( | |
75 bucket_storage_uri_class=cls.mock_bucket_storage_uri, | |
76 gsutil_api_class_map_factory=cls.mock_gsutil_api_class_map_factory) | |
77 | |
78 def setUp(self): | |
79 super(GsUtilUnitTestCase, self).setUp() | |
80 self.bucket_uris = [] | |
81 self.stdout_save = sys.stdout | |
82 self.stderr_save = sys.stderr | |
83 fd, self.stdout_file = tempfile.mkstemp() | |
84 sys.stdout = os.fdopen(fd, 'w+') | |
85 fd, self.stderr_file = tempfile.mkstemp() | |
86 sys.stderr = os.fdopen(fd, 'w+') | |
87 self.accumulated_stdout = [] | |
88 self.accumulated_stderr = [] | |
89 | |
90 self.root_logger = logging.getLogger() | |
91 self.is_debugging = self.root_logger.isEnabledFor(logging.DEBUG) | |
92 self.log_handlers_save = self.root_logger.handlers | |
93 fd, self.log_handler_file = tempfile.mkstemp() | |
94 self.log_handler_stream = os.fdopen(fd, 'w+') | |
95 self.temp_log_handler = GsutilStreamHandler(self.log_handler_stream) | |
96 self.root_logger.handlers = [self.temp_log_handler] | |
97 | |
98 def tearDown(self): | |
99 super(GsUtilUnitTestCase, self).tearDown() | |
100 | |
101 self.root_logger.handlers = self.log_handlers_save | |
102 self.temp_log_handler.flush() | |
103 self.temp_log_handler.close() | |
104 self.log_handler_stream.seek(0) | |
105 log_output = self.log_handler_stream.read() | |
106 self.log_handler_stream.close() | |
107 os.unlink(self.log_handler_file) | |
108 | |
109 sys.stdout.seek(0) | |
110 sys.stderr.seek(0) | |
111 stdout = sys.stdout.read() | |
112 stderr = sys.stderr.read() | |
113 stdout += ''.join(self.accumulated_stdout) | |
114 stderr += ''.join(self.accumulated_stderr) | |
115 sys.stdout.close() | |
116 sys.stderr.close() | |
117 sys.stdout = self.stdout_save | |
118 sys.stderr = self.stderr_save | |
119 os.unlink(self.stdout_file) | |
120 os.unlink(self.stderr_file) | |
121 | |
122 if self.is_debugging and stdout: | |
123 sys.stderr.write('==== stdout %s ====\n' % self.id()) | |
124 sys.stderr.write(stdout) | |
125 sys.stderr.write('==== end stdout ====\n') | |
126 if self.is_debugging and stderr: | |
127 sys.stderr.write('==== stderr %s ====\n' % self.id()) | |
128 sys.stderr.write(stderr) | |
129 sys.stderr.write('==== end stderr ====\n') | |
130 if self.is_debugging and log_output: | |
131 sys.stderr.write('==== log output %s ====\n' % self.id()) | |
132 sys.stderr.write(log_output) | |
133 sys.stderr.write('==== end log output ====\n') | |
134 | |
135 def RunCommand(self, command_name, args=None, headers=None, debug=0, | |
136 test_method=None, return_stdout=False, return_stderr=False, | |
137 return_log_handler=False, cwd=None): | |
138 """Method for calling gslib.command_runner.CommandRunner. | |
139 | |
140 Passes parallel_operations=False for all tests, optionally saving/returning | |
141 stdout output. We run all tests multi-threaded, to exercise those more | |
142 complicated code paths. | |
143 TODO: Change to run with parallel_operations=True for all tests. At | |
144 present when you do this it causes many test failures. | |
145 | |
146 Args: | |
147 command_name: The name of the command being run. | |
148 args: Command-line args (arg0 = actual arg, not command name ala bash). | |
149 headers: Dictionary containing optional HTTP headers to pass to boto. | |
150 debug: Debug level to pass in to boto connection (range 0..3). | |
151 test_method: Optional general purpose method for testing purposes. | |
152 Application and semantics of this method will vary by | |
153 command and test type. | |
154 return_stdout: If True, will save and return stdout produced by command. | |
155 return_stderr: If True, will save and return stderr produced by command. | |
156 return_log_handler: If True, will return a MockLoggingHandler instance | |
157 that was attached to the command's logger while running. | |
158 cwd: The working directory that should be switched to before running the | |
159 command. The working directory will be reset back to its original | |
160 value after running the command. If not specified, the working | |
161 directory is left unchanged. | |
162 | |
163 Returns: | |
164 One or a tuple of requested return values, depending on whether | |
165 return_stdout, return_stderr, and/or return_log_handler were specified. | |
166 """ | |
167 args = args or [] | |
168 | |
169 command_line = ' '.join([command_name] + args) | |
170 if self.is_debugging: | |
171 self.stderr_save.write('\nRunCommand of %s\n' % command_line) | |
172 | |
173 # Save and truncate stdout and stderr for the lifetime of RunCommand. This | |
174 # way, we can return just the stdout and stderr that was output during the | |
175 # RunNamedCommand call below. | |
176 sys.stdout.seek(0) | |
177 sys.stderr.seek(0) | |
178 stdout = sys.stdout.read() | |
179 stderr = sys.stderr.read() | |
180 if stdout: | |
181 self.accumulated_stdout.append(stdout) | |
182 if stderr: | |
183 self.accumulated_stderr.append(stderr) | |
184 sys.stdout.seek(0) | |
185 sys.stderr.seek(0) | |
186 sys.stdout.truncate() | |
187 sys.stderr.truncate() | |
188 | |
189 mock_log_handler = MockLoggingHandler() | |
190 logging.getLogger(command_name).addHandler(mock_log_handler) | |
191 | |
192 try: | |
193 with WorkingDirectory(cwd): | |
194 self.command_runner.RunNamedCommand( | |
195 command_name, args=args, headers=headers, debug=debug, | |
196 parallel_operations=False, test_method=test_method, | |
197 do_shutdown=False) | |
198 finally: | |
199 sys.stdout.seek(0) | |
200 stdout = sys.stdout.read() | |
201 sys.stderr.seek(0) | |
202 stderr = sys.stderr.read() | |
203 logging.getLogger(command_name).removeHandler(mock_log_handler) | |
204 mock_log_handler.close() | |
205 | |
206 log_output = '\n'.join( | |
207 '%s:\n ' % level + '\n '.join(records) | |
208 for level, records in mock_log_handler.messages.iteritems() | |
209 if records) | |
210 if self.is_debugging and log_output: | |
211 self.stderr_save.write( | |
212 '==== logging RunCommand %s %s ====\n' % (self.id(), command_line)) | |
213 self.stderr_save.write(log_output) | |
214 self.stderr_save.write('\n==== end logging ====\n') | |
215 if self.is_debugging and stdout: | |
216 self.stderr_save.write( | |
217 '==== stdout RunCommand %s %s ====\n' % (self.id(), command_line)) | |
218 self.stderr_save.write(stdout) | |
219 self.stderr_save.write('==== end stdout ====\n') | |
220 if self.is_debugging and stderr: | |
221 self.stderr_save.write( | |
222 '==== stderr RunCommand %s %s ====\n' % (self.id(), command_line)) | |
223 self.stderr_save.write(stderr) | |
224 self.stderr_save.write('==== end stderr ====\n') | |
225 | |
226 # Reset stdout and stderr files, so that we won't print them out again | |
227 # in tearDown if debugging is enabled. | |
228 sys.stdout.seek(0) | |
229 sys.stderr.seek(0) | |
230 sys.stdout.truncate() | |
231 sys.stderr.truncate() | |
232 | |
233 to_return = [] | |
234 if return_stdout: | |
235 to_return.append(stdout) | |
236 if return_stderr: | |
237 to_return.append(stderr) | |
238 if return_log_handler: | |
239 to_return.append(mock_log_handler) | |
240 if len(to_return) == 1: | |
241 return to_return[0] | |
242 return tuple(to_return) | |
243 | |
244 @classmethod | |
245 def MakeGsUtilApi(cls, debug=0): | |
246 gsutil_api_map = { | |
247 ApiMapConstants.API_MAP: ( | |
248 cls.mock_gsutil_api_class_map_factory.GetClassMap()), | |
249 ApiMapConstants.SUPPORT_MAP: { | |
250 'gs': [ApiSelector.XML, ApiSelector.JSON], | |
251 's3': [ApiSelector.XML] | |
252 }, | |
253 ApiMapConstants.DEFAULT_MAP: { | |
254 'gs': ApiSelector.JSON, | |
255 's3': ApiSelector.XML | |
256 } | |
257 } | |
258 | |
259 return CloudApiDelegator( | |
260 cls.mock_bucket_storage_uri, gsutil_api_map, cls.logger, debug=debug) | |
261 | |
262 @classmethod | |
263 def _test_wildcard_iterator(cls, uri_or_str, debug=0): | |
264 """Convenience method for instantiating a test instance of WildcardIterator. | |
265 | |
266 This makes it unnecessary to specify all the params of that class | |
267 (like bucket_storage_uri_class=mock_storage_service.MockBucketStorageUri). | |
268 Also, naming the factory method this way makes it clearer in the test code | |
269 that WildcardIterator needs to be set up for testing. | |
270 | |
271 Args are same as for wildcard_iterator.wildcard_iterator(), except | |
272 there are no class args for bucket_storage_uri_class or gsutil_api_class. | |
273 | |
274 Args: | |
275 uri_or_str: StorageUri or string representing the wildcard string. | |
276 debug: debug level to pass to the underlying connection (0..3) | |
277 | |
278 Returns: | |
279 WildcardIterator, over which caller can iterate. | |
280 """ | |
281 # TODO: Remove when tests no longer pass StorageUri arguments. | |
282 uri_string = uri_or_str | |
283 if hasattr(uri_or_str, 'uri'): | |
284 uri_string = uri_or_str.uri | |
285 | |
286 return wildcard_iterator.CreateWildcardIterator( | |
287 uri_string, cls.MakeGsUtilApi()) | |
288 | |
289 @staticmethod | |
290 def _test_storage_uri(uri_str, default_scheme='file', debug=0, | |
291 validate=True): | |
292 """Convenience method for instantiating a testing instance of StorageUri. | |
293 | |
294 This makes it unnecessary to specify | |
295 bucket_storage_uri_class=mock_storage_service.MockBucketStorageUri. | |
296 Also naming the factory method this way makes it clearer in the test | |
297 code that StorageUri needs to be set up for testing. | |
298 | |
299 Args, Returns, and Raises are same as for boto.storage_uri(), except there's | |
300 no bucket_storage_uri_class arg. | |
301 | |
302 Args: | |
303 uri_str: Uri string to create StorageUri for. | |
304 default_scheme: Default scheme for the StorageUri | |
305 debug: debug level to pass to the underlying connection (0..3) | |
306 validate: If True, validate the resource that the StorageUri refers to. | |
307 | |
308 Returns: | |
309 StorageUri based on the arguments. | |
310 """ | |
311 return boto.storage_uri(uri_str, default_scheme, debug, validate, | |
312 util.GSMockBucketStorageUri) | |
313 | |
314 def CreateBucket(self, bucket_name=None, test_objects=0, storage_class=None, | |
315 provider='gs'): | |
316 """Creates a test bucket. | |
317 | |
318 The bucket and all of its contents will be deleted after the test. | |
319 | |
320 Args: | |
321 bucket_name: Create the bucket with this name. If not provided, a | |
322 temporary test bucket name is constructed. | |
323 test_objects: The number of objects that should be placed in the bucket or | |
324 a list of object names to place in the bucket. Defaults to | |
325 0. | |
326 storage_class: storage class to use. If not provided we us standard. | |
327 provider: string provider to use, default gs. | |
328 | |
329 Returns: | |
330 StorageUri for the created bucket. | |
331 """ | |
332 bucket_name = bucket_name or self.MakeTempName('bucket') | |
333 bucket_uri = boto.storage_uri( | |
334 '%s://%s' % (provider, bucket_name.lower()), | |
335 suppress_consec_slashes=False, | |
336 bucket_storage_uri_class=util.GSMockBucketStorageUri) | |
337 bucket_uri.create_bucket(storage_class=storage_class) | |
338 self.bucket_uris.append(bucket_uri) | |
339 try: | |
340 iter(test_objects) | |
341 except TypeError: | |
342 test_objects = [self.MakeTempName('obj') for _ in range(test_objects)] | |
343 for i, name in enumerate(test_objects): | |
344 self.CreateObject(bucket_uri=bucket_uri, object_name=name, | |
345 contents='test %d' % i) | |
346 return bucket_uri | |
347 | |
348 def CreateObject(self, bucket_uri=None, object_name=None, contents=None): | |
349 """Creates a test object. | |
350 | |
351 Args: | |
352 bucket_uri: The URI of the bucket to place the object in. If not | |
353 specified, a new temporary bucket is created. | |
354 object_name: The name to use for the object. If not specified, a temporary | |
355 test object name is constructed. | |
356 contents: The contents to write to the object. If not specified, the key | |
357 is not written to, which means that it isn't actually created | |
358 yet on the server. | |
359 | |
360 Returns: | |
361 A StorageUri for the created object. | |
362 """ | |
363 bucket_uri = bucket_uri or self.CreateBucket() | |
364 object_name = object_name or self.MakeTempName('obj') | |
365 key_uri = bucket_uri.clone_replace_name(object_name) | |
366 if contents is not None: | |
367 key_uri.set_contents_from_string(contents) | |
368 return key_uri | |
OLD | NEW |