Index: third_party/gsutil/gslib/tests/testcase/unit_testcase.py |
diff --git a/third_party/gsutil/gslib/tests/testcase/unit_testcase.py b/third_party/gsutil/gslib/tests/testcase/unit_testcase.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e36bd285c714aaf5bd29da7b19116a8a7db60733 |
--- /dev/null |
+++ b/third_party/gsutil/gslib/tests/testcase/unit_testcase.py |
@@ -0,0 +1,368 @@ |
+# -*- coding: utf-8 -*- |
+# Copyright 2013 Google Inc. All Rights Reserved. |
+# |
+# 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. |
+"""Contains gsutil base unit test case class.""" |
+ |
+from __future__ import absolute_import |
+ |
+import logging |
+import os |
+import sys |
+import tempfile |
+ |
+import boto |
+from gslib import wildcard_iterator |
+from gslib.boto_translation import BotoTranslation |
+from gslib.cloud_api_delegator import CloudApiDelegator |
+from gslib.command_runner import CommandRunner |
+from gslib.cs_api_map import ApiMapConstants |
+from gslib.cs_api_map import ApiSelector |
+from gslib.tests.mock_logging_handler import MockLoggingHandler |
+from gslib.tests.testcase import base |
+import gslib.tests.util as util |
+from gslib.tests.util import unittest |
+from gslib.tests.util import WorkingDirectory |
+from gslib.util import GsutilStreamHandler |
+ |
+ |
+class GsutilApiUnitTestClassMapFactory(object): |
+ """Class map factory for use in unit tests. |
+ |
+ BotoTranslation is used for all cases so that GSMockBucketStorageUri can |
+ be used to communicate with the mock XML service. |
+ """ |
+ |
+ @classmethod |
+ def GetClassMap(cls): |
+ """Returns a class map for use in unit tests.""" |
+ gs_class_map = { |
+ ApiSelector.XML: BotoTranslation, |
+ ApiSelector.JSON: BotoTranslation |
+ } |
+ s3_class_map = { |
+ ApiSelector.XML: BotoTranslation |
+ } |
+ class_map = { |
+ 'gs': gs_class_map, |
+ 's3': s3_class_map |
+ } |
+ return class_map |
+ |
+ |
+@unittest.skipUnless(util.RUN_UNIT_TESTS, |
+ 'Not running integration tests.') |
+class GsUtilUnitTestCase(base.GsUtilTestCase): |
+ """Base class for gsutil unit tests.""" |
+ |
+ @classmethod |
+ def setUpClass(cls): |
+ base.GsUtilTestCase.setUpClass() |
+ cls.mock_bucket_storage_uri = util.GSMockBucketStorageUri |
+ cls.mock_gsutil_api_class_map_factory = GsutilApiUnitTestClassMapFactory |
+ cls.logger = logging.getLogger() |
+ cls.command_runner = CommandRunner( |
+ bucket_storage_uri_class=cls.mock_bucket_storage_uri, |
+ gsutil_api_class_map_factory=cls.mock_gsutil_api_class_map_factory) |
+ |
+ def setUp(self): |
+ super(GsUtilUnitTestCase, self).setUp() |
+ self.bucket_uris = [] |
+ self.stdout_save = sys.stdout |
+ self.stderr_save = sys.stderr |
+ fd, self.stdout_file = tempfile.mkstemp() |
+ sys.stdout = os.fdopen(fd, 'w+') |
+ fd, self.stderr_file = tempfile.mkstemp() |
+ sys.stderr = os.fdopen(fd, 'w+') |
+ self.accumulated_stdout = [] |
+ self.accumulated_stderr = [] |
+ |
+ self.root_logger = logging.getLogger() |
+ self.is_debugging = self.root_logger.isEnabledFor(logging.DEBUG) |
+ self.log_handlers_save = self.root_logger.handlers |
+ fd, self.log_handler_file = tempfile.mkstemp() |
+ self.log_handler_stream = os.fdopen(fd, 'w+') |
+ self.temp_log_handler = GsutilStreamHandler(self.log_handler_stream) |
+ self.root_logger.handlers = [self.temp_log_handler] |
+ |
+ def tearDown(self): |
+ super(GsUtilUnitTestCase, self).tearDown() |
+ |
+ self.root_logger.handlers = self.log_handlers_save |
+ self.temp_log_handler.flush() |
+ self.temp_log_handler.close() |
+ self.log_handler_stream.seek(0) |
+ log_output = self.log_handler_stream.read() |
+ self.log_handler_stream.close() |
+ os.unlink(self.log_handler_file) |
+ |
+ sys.stdout.seek(0) |
+ sys.stderr.seek(0) |
+ stdout = sys.stdout.read() |
+ stderr = sys.stderr.read() |
+ stdout += ''.join(self.accumulated_stdout) |
+ stderr += ''.join(self.accumulated_stderr) |
+ sys.stdout.close() |
+ sys.stderr.close() |
+ sys.stdout = self.stdout_save |
+ sys.stderr = self.stderr_save |
+ os.unlink(self.stdout_file) |
+ os.unlink(self.stderr_file) |
+ |
+ if self.is_debugging and stdout: |
+ sys.stderr.write('==== stdout %s ====\n' % self.id()) |
+ sys.stderr.write(stdout) |
+ sys.stderr.write('==== end stdout ====\n') |
+ if self.is_debugging and stderr: |
+ sys.stderr.write('==== stderr %s ====\n' % self.id()) |
+ sys.stderr.write(stderr) |
+ sys.stderr.write('==== end stderr ====\n') |
+ if self.is_debugging and log_output: |
+ sys.stderr.write('==== log output %s ====\n' % self.id()) |
+ sys.stderr.write(log_output) |
+ sys.stderr.write('==== end log output ====\n') |
+ |
+ def RunCommand(self, command_name, args=None, headers=None, debug=0, |
+ test_method=None, return_stdout=False, return_stderr=False, |
+ return_log_handler=False, cwd=None): |
+ """Method for calling gslib.command_runner.CommandRunner. |
+ |
+ Passes parallel_operations=False for all tests, optionally saving/returning |
+ stdout output. We run all tests multi-threaded, to exercise those more |
+ complicated code paths. |
+ TODO: Change to run with parallel_operations=True for all tests. At |
+ present when you do this it causes many test failures. |
+ |
+ Args: |
+ command_name: The name of the command being run. |
+ args: Command-line args (arg0 = actual arg, not command name ala bash). |
+ headers: Dictionary containing optional HTTP headers to pass to boto. |
+ debug: Debug level to pass in to boto connection (range 0..3). |
+ test_method: Optional general purpose method for testing purposes. |
+ Application and semantics of this method will vary by |
+ command and test type. |
+ return_stdout: If True, will save and return stdout produced by command. |
+ return_stderr: If True, will save and return stderr produced by command. |
+ return_log_handler: If True, will return a MockLoggingHandler instance |
+ that was attached to the command's logger while running. |
+ cwd: The working directory that should be switched to before running the |
+ command. The working directory will be reset back to its original |
+ value after running the command. If not specified, the working |
+ directory is left unchanged. |
+ |
+ Returns: |
+ One or a tuple of requested return values, depending on whether |
+ return_stdout, return_stderr, and/or return_log_handler were specified. |
+ """ |
+ args = args or [] |
+ |
+ command_line = ' '.join([command_name] + args) |
+ if self.is_debugging: |
+ self.stderr_save.write('\nRunCommand of %s\n' % command_line) |
+ |
+ # Save and truncate stdout and stderr for the lifetime of RunCommand. This |
+ # way, we can return just the stdout and stderr that was output during the |
+ # RunNamedCommand call below. |
+ sys.stdout.seek(0) |
+ sys.stderr.seek(0) |
+ stdout = sys.stdout.read() |
+ stderr = sys.stderr.read() |
+ if stdout: |
+ self.accumulated_stdout.append(stdout) |
+ if stderr: |
+ self.accumulated_stderr.append(stderr) |
+ sys.stdout.seek(0) |
+ sys.stderr.seek(0) |
+ sys.stdout.truncate() |
+ sys.stderr.truncate() |
+ |
+ mock_log_handler = MockLoggingHandler() |
+ logging.getLogger(command_name).addHandler(mock_log_handler) |
+ |
+ try: |
+ with WorkingDirectory(cwd): |
+ self.command_runner.RunNamedCommand( |
+ command_name, args=args, headers=headers, debug=debug, |
+ parallel_operations=False, test_method=test_method, |
+ do_shutdown=False) |
+ finally: |
+ sys.stdout.seek(0) |
+ stdout = sys.stdout.read() |
+ sys.stderr.seek(0) |
+ stderr = sys.stderr.read() |
+ logging.getLogger(command_name).removeHandler(mock_log_handler) |
+ mock_log_handler.close() |
+ |
+ log_output = '\n'.join( |
+ '%s:\n ' % level + '\n '.join(records) |
+ for level, records in mock_log_handler.messages.iteritems() |
+ if records) |
+ if self.is_debugging and log_output: |
+ self.stderr_save.write( |
+ '==== logging RunCommand %s %s ====\n' % (self.id(), command_line)) |
+ self.stderr_save.write(log_output) |
+ self.stderr_save.write('\n==== end logging ====\n') |
+ if self.is_debugging and stdout: |
+ self.stderr_save.write( |
+ '==== stdout RunCommand %s %s ====\n' % (self.id(), command_line)) |
+ self.stderr_save.write(stdout) |
+ self.stderr_save.write('==== end stdout ====\n') |
+ if self.is_debugging and stderr: |
+ self.stderr_save.write( |
+ '==== stderr RunCommand %s %s ====\n' % (self.id(), command_line)) |
+ self.stderr_save.write(stderr) |
+ self.stderr_save.write('==== end stderr ====\n') |
+ |
+ # Reset stdout and stderr files, so that we won't print them out again |
+ # in tearDown if debugging is enabled. |
+ sys.stdout.seek(0) |
+ sys.stderr.seek(0) |
+ sys.stdout.truncate() |
+ sys.stderr.truncate() |
+ |
+ to_return = [] |
+ if return_stdout: |
+ to_return.append(stdout) |
+ if return_stderr: |
+ to_return.append(stderr) |
+ if return_log_handler: |
+ to_return.append(mock_log_handler) |
+ if len(to_return) == 1: |
+ return to_return[0] |
+ return tuple(to_return) |
+ |
+ @classmethod |
+ def MakeGsUtilApi(cls, debug=0): |
+ gsutil_api_map = { |
+ ApiMapConstants.API_MAP: ( |
+ cls.mock_gsutil_api_class_map_factory.GetClassMap()), |
+ ApiMapConstants.SUPPORT_MAP: { |
+ 'gs': [ApiSelector.XML, ApiSelector.JSON], |
+ 's3': [ApiSelector.XML] |
+ }, |
+ ApiMapConstants.DEFAULT_MAP: { |
+ 'gs': ApiSelector.JSON, |
+ 's3': ApiSelector.XML |
+ } |
+ } |
+ |
+ return CloudApiDelegator( |
+ cls.mock_bucket_storage_uri, gsutil_api_map, cls.logger, debug=debug) |
+ |
+ @classmethod |
+ def _test_wildcard_iterator(cls, uri_or_str, debug=0): |
+ """Convenience method for instantiating a test instance of WildcardIterator. |
+ |
+ This makes it unnecessary to specify all the params of that class |
+ (like bucket_storage_uri_class=mock_storage_service.MockBucketStorageUri). |
+ Also, naming the factory method this way makes it clearer in the test code |
+ that WildcardIterator needs to be set up for testing. |
+ |
+ Args are same as for wildcard_iterator.wildcard_iterator(), except |
+ there are no class args for bucket_storage_uri_class or gsutil_api_class. |
+ |
+ Args: |
+ uri_or_str: StorageUri or string representing the wildcard string. |
+ debug: debug level to pass to the underlying connection (0..3) |
+ |
+ Returns: |
+ WildcardIterator, over which caller can iterate. |
+ """ |
+ # TODO: Remove when tests no longer pass StorageUri arguments. |
+ uri_string = uri_or_str |
+ if hasattr(uri_or_str, 'uri'): |
+ uri_string = uri_or_str.uri |
+ |
+ return wildcard_iterator.CreateWildcardIterator( |
+ uri_string, cls.MakeGsUtilApi()) |
+ |
+ @staticmethod |
+ def _test_storage_uri(uri_str, default_scheme='file', debug=0, |
+ validate=True): |
+ """Convenience method for instantiating a testing instance of StorageUri. |
+ |
+ This makes it unnecessary to specify |
+ bucket_storage_uri_class=mock_storage_service.MockBucketStorageUri. |
+ Also naming the factory method this way makes it clearer in the test |
+ code that StorageUri needs to be set up for testing. |
+ |
+ Args, Returns, and Raises are same as for boto.storage_uri(), except there's |
+ no bucket_storage_uri_class arg. |
+ |
+ Args: |
+ uri_str: Uri string to create StorageUri for. |
+ default_scheme: Default scheme for the StorageUri |
+ debug: debug level to pass to the underlying connection (0..3) |
+ validate: If True, validate the resource that the StorageUri refers to. |
+ |
+ Returns: |
+ StorageUri based on the arguments. |
+ """ |
+ return boto.storage_uri(uri_str, default_scheme, debug, validate, |
+ util.GSMockBucketStorageUri) |
+ |
+ def CreateBucket(self, bucket_name=None, test_objects=0, storage_class=None, |
+ provider='gs'): |
+ """Creates a test bucket. |
+ |
+ The bucket and all of its contents will be deleted after the test. |
+ |
+ Args: |
+ bucket_name: Create the bucket with this name. If not provided, a |
+ temporary test bucket name is constructed. |
+ test_objects: The number of objects that should be placed in the bucket or |
+ a list of object names to place in the bucket. Defaults to |
+ 0. |
+ storage_class: storage class to use. If not provided we us standard. |
+ provider: string provider to use, default gs. |
+ |
+ Returns: |
+ StorageUri for the created bucket. |
+ """ |
+ bucket_name = bucket_name or self.MakeTempName('bucket') |
+ bucket_uri = boto.storage_uri( |
+ '%s://%s' % (provider, bucket_name.lower()), |
+ suppress_consec_slashes=False, |
+ bucket_storage_uri_class=util.GSMockBucketStorageUri) |
+ bucket_uri.create_bucket(storage_class=storage_class) |
+ self.bucket_uris.append(bucket_uri) |
+ try: |
+ iter(test_objects) |
+ except TypeError: |
+ test_objects = [self.MakeTempName('obj') for _ in range(test_objects)] |
+ for i, name in enumerate(test_objects): |
+ self.CreateObject(bucket_uri=bucket_uri, object_name=name, |
+ contents='test %d' % i) |
+ return bucket_uri |
+ |
+ def CreateObject(self, bucket_uri=None, object_name=None, contents=None): |
+ """Creates a test object. |
+ |
+ Args: |
+ bucket_uri: The URI of the bucket to place the object in. If not |
+ specified, a new temporary bucket is created. |
+ object_name: The name to use for the object. If not specified, a temporary |
+ test object name is constructed. |
+ contents: The contents to write to the object. If not specified, the key |
+ is not written to, which means that it isn't actually created |
+ yet on the server. |
+ |
+ Returns: |
+ A StorageUri for the created object. |
+ """ |
+ bucket_uri = bucket_uri or self.CreateBucket() |
+ object_name = object_name or self.MakeTempName('obj') |
+ key_uri = bucket_uri.clone_replace_name(object_name) |
+ if contents is not None: |
+ key_uri.set_contents_from_string(contents) |
+ return key_uri |