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 |