Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing storage regression tests to match gcloud-node. #319

Merged
merged 1 commit into from
Nov 6, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,10 @@ Running Regression Tests
so you'll need to provide some environment variables to facilitate
authentication to your project:

- ``GCLOUD_TESTS_PROJECT_ID``: Developers Console project ID (e.g.
bamboo-shift-455).
- ``GCLOUD_TESTS_DATASET_ID``: The name of the dataset your tests connect to.
This is typically the same as ``GCLOUD_TESTS_PROJECT_ID``.
- ``GCLOUD_TESTS_CLIENT_EMAIL``: The email for the service account you're
authenticating with
- ``GCLOUD_TESTS_KEY_FILE``: The path to an encrypted key file.
Expand Down
3 changes: 2 additions & 1 deletion gcloud/storage/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,8 @@ def upload_file(self, filename, key=None):
if key is None:
key = os.path.basename(filename)
key = self.new_key(key)
return key.upload_from_filename(filename)
key.upload_from_filename(filename)
return key

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


def upload_file_object(self, file_obj, key=None):
"""Shortcut method to upload a file object into this bucket.
Expand Down
Binary file added regression/data/CloudPlatform_128px_Retina.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added regression/data/five-mb-file.zip
Binary file not shown.
3 changes: 2 additions & 1 deletion regression/local_test_setup.sample
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export GCLOUD_TESTS_DATASET_ID="my-dataset"
export GCLOUD_TESTS_PROJECT_ID="my-project"
export GCLOUD_TESTS_DATASET_ID=${GCLOUD_TESTS_PROJECT_ID}
export GCLOUD_TESTS_CLIENT_EMAIL="[email protected]"
export GCLOUD_TESTS_KEY_FILE="path.key"
39 changes: 30 additions & 9 deletions regression/regression_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,58 @@
import sys

from gcloud import datastore
from gcloud import storage


# Defaults from shell environ. May be None.
PROJECT_ID = os.getenv('GCLOUD_TESTS_PROJECT_ID')
DATASET_ID = os.getenv('GCLOUD_TESTS_DATASET_ID')
CLIENT_EMAIL = os.getenv('GCLOUD_TESTS_CLIENT_EMAIL')
KEY_FILENAME = os.getenv('GCLOUD_TESTS_KEY_FILE')
DATASETS = {}
CACHED_RETURN_VALS = {}

ENVIRON_ERROR_MSG = """\
To run the regression tests, you need to set some environment variables.
Please check the Contributing guide for instructions.
"""


def get_environ():
if DATASET_ID is None or CLIENT_EMAIL is None or KEY_FILENAME is None:
print >> sys.stderr, ENVIRON_ERROR_MSG
sys.exit(1)
def get_environ(require_datastore=False, require_storage=False):
if require_datastore:
if DATASET_ID is None or CLIENT_EMAIL is None or KEY_FILENAME is None:
print >> sys.stderr, ENVIRON_ERROR_MSG
sys.exit(1)

if require_storage:
if PROJECT_ID is None or CLIENT_EMAIL is None or KEY_FILENAME is None:
print >> sys.stderr, ENVIRON_ERROR_MSG
sys.exit(1)

return {
'project_id': PROJECT_ID,
'dataset_id': DATASET_ID,
'client_email': CLIENT_EMAIL,
'key_filename': KEY_FILENAME,
}


def get_dataset():
environ = get_environ()
environ = get_environ(require_datastore=True)
get_dataset_args = (environ['dataset_id'], environ['client_email'],
environ['key_filename'])
if get_dataset_args not in DATASETS:
key = ('get_dataset', get_dataset_args)
if key not in CACHED_RETURN_VALS:
# Cache return value for the environment.
CACHED_RETURN_VALS[key] = datastore.get_dataset(*get_dataset_args)
return CACHED_RETURN_VALS[key]


def get_storage_connection():
environ = get_environ(require_storage=True)
get_connection_args = (environ['project_id'], environ['client_email'],
environ['key_filename'])
key = ('get_storage_connection', get_connection_args)
if key not in CACHED_RETURN_VALS:
# Cache return value for the environment.
DATASETS[get_dataset_args] = datastore.get_dataset(*get_dataset_args)
return DATASETS[get_dataset_args]
CACHED_RETURN_VALS[key] = storage.get_connection(*get_connection_args)
return CACHED_RETURN_VALS[key]
7 changes: 5 additions & 2 deletions regression/run_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def get_parser():
parser = argparse.ArgumentParser(
description='GCloud test runner against actual project.')
parser.add_argument('--package', dest='package',
choices=('datastore',),
choices=('datastore', 'storage'),
default='datastore', help='Package to be tested.')
return parser

Expand All @@ -27,7 +27,10 @@ def main():
parser = get_parser()
args = parser.parse_args()
# Make sure environ is set before running test.
regression_utils.get_environ()
if args.package == 'datastore':
regression_utils.get_environ(require_datastore=True)
elif args.package == 'storage':
regression_utils.get_environ(require_storage=True)
test_result = run_module_tests(args.package)
if not test_result.wasSuccessful():
sys.exit(1)
Expand Down
247 changes: 247 additions & 0 deletions regression/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
from Crypto.Hash import MD5
import base64
import httplib2
import tempfile
import time
import unittest2

from gcloud import storage
# This assumes the command is being run via tox hence the
# repository root is the current directory.
from regression import regression_utils


HTTP = httplib2.Http()
SHARED_BUCKETS = {}


def setUpModule():
if 'test_bucket' not in SHARED_BUCKETS:
connection = regression_utils.get_storage_connection()
# %d rounds milliseconds to nearest integer.
bucket_name = 'new%d' % (1000 * time.time(),)
# In the **very** rare case the bucket name is reserved, this
# fails with a ConnectionError.
SHARED_BUCKETS['test_bucket'] = connection.create_bucket(bucket_name)


def tearDownModule():
for bucket in SHARED_BUCKETS.values():
# Passing force=True also deletes all files.
bucket.delete(force=True)


class TestStorage(unittest2.TestCase):

@classmethod
def setUpClass(cls):
cls.connection = regression_utils.get_storage_connection()


class TestStorageBuckets(TestStorage):

def setUp(self):
self.case_buckets_to_delete = []

def tearDown(self):
for bucket in self.case_buckets_to_delete:
bucket.delete()

def test_create_bucket(self):
new_bucket_name = 'a-new-bucket'
self.assertRaises(storage.exceptions.NotFoundError,
self.connection.get_bucket, new_bucket_name)
created = self.connection.create_bucket(new_bucket_name)
self.case_buckets_to_delete.append(created)
self.assertEqual(created.name, new_bucket_name)

def test_get_buckets(self):
buckets_to_create = [
'new%d' % (1000 * time.time(),),
'newer%d' % (1000 * time.time(),),
'newest%d' % (1000 * time.time(),),
]
created_buckets = []
for bucket_name in buckets_to_create:
bucket = self.connection.create_bucket(bucket_name)
self.case_buckets_to_delete.append(bucket)

# Retrieve the buckets.
all_buckets = self.connection.get_all_buckets()
created_buckets = [bucket for bucket in all_buckets
if bucket.name in buckets_to_create]
self.assertEqual(len(created_buckets), len(buckets_to_create))

This comment was marked as spam.

This comment was marked as spam.


class TestStorageFiles(TestStorage):

FILES = {
'logo': {
'path': 'regression/data/CloudPlatform_128px_Retina.png',
},
'big': {
'path': 'regression/data/five-mb-file.zip',
},
}

@staticmethod
def _get_base64_md5hash(filename):
with open(filename, 'rb') as file_obj:
hash = MD5.new(data=file_obj.read())
digest_bytes = hash.digest()
return base64.b64encode(digest_bytes)

@classmethod
def setUpClass(cls):
super(TestStorageFiles, cls).setUpClass()
for file_data in cls.FILES.values():
file_data['hash'] = cls._get_base64_md5hash(file_data['path'])
cls.bucket = SHARED_BUCKETS['test_bucket']

def setUp(self):
self.case_keys_to_delete = []

def tearDown(self):
for key in self.case_keys_to_delete:
key.delete()


class TestStorageWriteFiles(TestStorageFiles):

def test_large_file_write_from_stream(self):
key = self.bucket.new_key('LargeFile')
self.assertEqual(key.metadata, {})

file_data = self.FILES['big']
with open(file_data['path'], 'rb') as file_obj:
self.bucket.upload_file_object(file_obj, key=key)
self.case_keys_to_delete.append(key)

key.reload_metadata()
self.assertEqual(key.metadata['md5Hash'], file_data['hash'])

def test_write_metadata(self):
my_metadata = {'contentType': 'image/png'}
key = self.bucket.upload_file(self.FILES['logo']['path'])
self.case_keys_to_delete.append(key)

# NOTE: This should not be necessary. We should be able to pass
# it in to upload_file and also to upload_from_string.
key.patch_metadata(my_metadata)
self.assertEqual(key.metadata['contentType'],
my_metadata['contentType'])

def test_direct_write_and_read_into_file(self):
key = self.bucket.new_key('MyBuffer')
file_contents = 'Hello World'
key.upload_from_string(file_contents)
self.case_keys_to_delete.append(key)

same_key = self.bucket.new_key('MyBuffer')
temp_filename = tempfile.mktemp()
with open(temp_filename, 'w') as file_obj:
same_key.get_contents_to_file(file_obj)

with open(temp_filename, 'rb') as file_obj:
stored_contents = file_obj.read()

self.assertEqual(file_contents, stored_contents)

def test_copy_existing_file(self):
key = self.bucket.upload_file(self.FILES['logo']['path'],
key='CloudLogo')
self.case_keys_to_delete.append(key)

new_key = self.bucket.copy_key(key, self.bucket, 'CloudLogoCopy')
self.case_keys_to_delete.append(new_key)

base_contents = key.get_contents_as_string()
copied_contents = new_key.get_contents_as_string()
self.assertEqual(base_contents, copied_contents)


class TestStorageListFiles(TestStorageFiles):

FILENAMES = ['CloudLogo1', 'CloudLogo2', 'CloudLogo3']

@classmethod
def setUpClass(cls):
super(TestStorageListFiles, cls).setUpClass()
# Make sure bucket empty before beginning.
for key in cls.bucket:
key.delete()

logo_path = cls.FILES['logo']['path']
key = cls.bucket.upload_file(logo_path, key=cls.FILENAMES[0])
cls.suite_keys_to_delete = [key]

# Copy main key onto remaining in FILENAMES.
for filename in cls.FILENAMES[1:]:
new_key = cls.bucket.copy_key(key, cls.bucket, filename)
cls.suite_keys_to_delete.append(new_key)

@classmethod
def tearDownClass(cls):
for key in cls.suite_keys_to_delete:
key.delete()

def test_list_files(self):
all_keys = self.bucket.get_all_keys()
self.assertEqual(len(all_keys), len(self.FILENAMES))

def test_paginate_files(self):
truncation_size = 1
extra_params = {'maxResults': len(self.FILENAMES) - truncation_size}
iterator = storage.key._KeyIterator(bucket=self.bucket,
extra_params=extra_params)
response = iterator.get_next_page_response()
keys = list(iterator.get_items_from_response(response))
self.assertEqual(len(keys), extra_params['maxResults'])
self.assertEqual(iterator.page_number, 1)
self.assertTrue(iterator.next_page_token is not None)

response = iterator.get_next_page_response()
last_keys = list(iterator.get_items_from_response(response))
self.assertEqual(len(last_keys), truncation_size)


class TestStorageSignURLs(TestStorageFiles):

def setUp(self):
super(TestStorageSignURLs, self).setUp()

logo_path = self.FILES['logo']['path']
with open(logo_path, 'r') as file_obj:
self.LOCAL_FILE = file_obj.read()

key = self.bucket.new_key('LogoToSign.jpg')
key.upload_from_string(self.LOCAL_FILE)
self.case_keys_to_delete.append(key)

def tearDown(self):
for key in self.case_keys_to_delete:
if key.exists():
key.delete()

def test_create_signed_read_url(self):
key = self.bucket.new_key('LogoToSign.jpg')
expiration = int(time.time() + 5)
signed_url = key.generate_signed_url(expiration, method='GET')

response, content = HTTP.request(signed_url, method='GET')
self.assertEqual(response.status, 200)
self.assertEqual(content, self.LOCAL_FILE)

def test_create_signed_delete_url(self):
key = self.bucket.new_key('LogoToSign.jpg')
expiration = int(time.time() + 283473274)
signed_delete_url = key.generate_signed_url(expiration,
method='DELETE')

response, content = HTTP.request(signed_delete_url, method='DELETE')
self.assertEqual(response.status, 204)
self.assertEqual(content, '')

# Check that the key has actually been deleted.
self.assertRaises(storage.exceptions.NotFoundError,
key.reload_metadata)
1 change: 1 addition & 0 deletions scripts/run_regression.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ fi

# Run the regression tests for each tested package.
python regression/run_regression.py --package datastore
python regression/run_regression.py --package storage