Merge lp:~jaypipes/glance/testing-overhaul into lp:~hudson-openstack/glance/trunk

Proposed by Jay Pipes
Status: Merged
Approved by: Jay Pipes
Approved revision: 14
Merged at revision: 12
Proposed branch: lp:~jaypipes/glance/testing-overhaul
Merge into: lp:~hudson-openstack/glance/trunk
Diff against target: 897 lines (+519/-129)
15 files modified
.bzrignore (+1/-0)
glance/parallax/controllers.py (+0/-8)
glance/teller/backends/__init__.py (+2/-15)
glance/teller/backends/http.py (+0/-2)
glance/teller/backends/swift.py (+7/-5)
glance/teller/controllers.py (+5/-2)
glance/teller/registries.py (+12/-28)
run_tests.sh (+66/-0)
tests/stubs.py (+149/-0)
tests/unit/test_teller_api.py (+34/-10)
tests/unit/test_teller_backends.py (+60/-59)
tests/utils.py (+27/-0)
tools/install_venv.py (+136/-0)
tools/pip-requires (+16/-0)
tools/with_venv.sh (+4/-0)
To merge this branch: bzr merge lp:~jaypipes/glance/testing-overhaul
Reviewer Review Type Date Requested Status
Rick Harris (community) Approve
Review via email: mp+38147@code.launchpad.net

Description of the change

This patch overhauls the testing in Glance:

* Adds Nova-like run_tests.sh and tools/* files to automatically run unit tests in a virtual environment. Just do ./run_tests.sh -V from project root. All required dependencies will be installed into a new virtual environment at .glance-venv.
* Adds proper mocking and stubouts using pymox. This removes the need for all the FakeParallaxAdapter and similar code. Unit tests now call stubs.stub_out_parallax(), etc instead of having swiftfakehttp.py and similar code.

To post a comment you must log in.
lp:~jaypipes/glance/testing-overhaul updated
14. By Jay Pipes

unittest2 -> unittest. For now, since not using unittest2 features yet.

Revision history for this message
Rick Harris (rconradharris) wrote :

Excellent work. I really like:

* Renaming the Adapter classes to something a little less verbose
* Axing the TestStrBackend :) That was a nasty hack.
* The use of `PyMox`. Was not familiar with that, will have to read up on it. Looks very clean.
* run-tests, obviously, very handy

I was going to add a small nit with the aliasing of unittest2 as unittest since unlike cString and cPickle they aren't entirely compatible API's. However, looks like you *just* removed it entirely, so we're good.

review: Approve
Revision history for this message
Jay Pipes (jaypipes) wrote :

ya, see the IRC convo with mtaylor about unittest2. Wasn't using it's features yet, so I axed it.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2010-09-27 22:43:04 +0000
+++ .bzrignore 2010-10-11 19:36:15 +0000
@@ -1,2 +1,3 @@
1*.pyc1*.pyc
2glance.egg-info2glance.egg-info
3glance.sqlite
34
=== modified file 'glance/parallax/controllers.py'
--- glance/parallax/controllers.py 2010-10-04 22:18:12 +0000
+++ glance/parallax/controllers.py 2010-10-11 19:36:15 +0000
@@ -97,11 +97,3 @@
97 mapper.resource("image", "images", controller=ImageController(),97 mapper.resource("image", "images", controller=ImageController(),
98 collection={'detail': 'GET'})98 collection={'detail': 'GET'})
99 super(API, self).__init__(mapper)99 super(API, self).__init__(mapper)
100
101
102
103
104
105
106
107
108100
=== modified file 'glance/teller/backends/__init__.py'
--- glance/teller/backends/__init__.py 2010-10-04 22:18:12 +0000
+++ glance/teller/backends/__init__.py 2010-10-11 19:36:15 +0000
@@ -39,20 +39,9 @@
39 CHUNKSIZE = 409639 CHUNKSIZE = 4096
4040
4141
42class TestStrBackend(Backend):
43 """ Backend used for testing """
44
45 @classmethod
46 def get(cls, parsed_uri, expected_size):
47 """
48 teststr://data
49 """
50 yield parsed_uri.path
51
52
53class FilesystemBackend(Backend):42class FilesystemBackend(Backend):
54 @classmethod43 @classmethod
55 def get(cls, parsed_uri, expected_size, opener=lambda p: open(p, "b")):44 def get(cls, parsed_uri, expected_size, opener=lambda p: open(p, "rb")):
56 """ Filesystem-based backend45 """ Filesystem-based backend
5746
58 file:///path/to/file.tar.gz.047 file:///path/to/file.tar.gz.0
@@ -72,8 +61,7 @@
72 "file": FilesystemBackend,61 "file": FilesystemBackend,
73 "http": HTTPBackend,62 "http": HTTPBackend,
74 "https": HTTPBackend,63 "https": HTTPBackend,
75 "swift": SwiftBackend,64 "swift": SwiftBackend
76 "teststr": TestStrBackend
77 }65 }
7866
79 parsed_uri = urlparse.urlparse(uri)67 parsed_uri = urlparse.urlparse(uri)
@@ -85,4 +73,3 @@
85 raise UnsupportedBackend("No backend found for '%s'" % scheme)73 raise UnsupportedBackend("No backend found for '%s'" % scheme)
8674
87 return backend.get(parsed_uri, **kwargs)75 return backend.get(parsed_uri, **kwargs)
88
8976
=== modified file 'glance/teller/backends/http.py'
--- glance/teller/backends/http.py 2010-10-04 22:18:12 +0000
+++ glance/teller/backends/http.py 2010-10-11 19:36:15 +0000
@@ -43,5 +43,3 @@
43 return backends._file_iter(conn.getresponse(), cls.CHUNKSIZE)43 return backends._file_iter(conn.getresponse(), cls.CHUNKSIZE)
44 finally:44 finally:
45 conn.close()45 conn.close()
46
47
4846
=== modified file 'glance/teller/backends/swift.py'
--- glance/teller/backends/swift.py 2010-10-04 22:18:12 +0000
+++ glance/teller/backends/swift.py 2010-10-11 19:36:15 +0000
@@ -15,7 +15,6 @@
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.16# under the License.
1717
18import cloudfiles
19from glance.teller.backends import Backend, BackendException18from glance.teller.backends import Backend, BackendException
2019
2120
@@ -39,6 +38,10 @@
39 if conn_class:38 if conn_class:
40 pass # Use the provided conn_class39 pass # Use the provided conn_class
41 else:40 else:
41 # Import cloudfiles here because stubout will replace this call
42 # with a faked swift client in the unittests, avoiding import
43 # errors if the test system does not have cloudfiles installed
44 import cloudfiles
42 conn_class = cloudfiles45 conn_class = cloudfiles
4346
44 swift_conn = conn_class.get_connection(username=user, api_key=api_key,47 swift_conn = conn_class.get_connection(username=user, api_key=api_key,
@@ -64,14 +67,15 @@
64 3) reassemble authurl67 3) reassemble authurl
65 """68 """
66 path = parsed_uri.path.lstrip('//')69 path = parsed_uri.path.lstrip('//')
70 netloc = parsed_uri.netloc
6771
68 try:72 try:
69 creds, path = path.split('@')73 creds, netloc = netloc.split('@')
70 user, api_key = creds.split(':')74 user, api_key = creds.split(':')
71 path_parts = path.split('/')75 path_parts = path.split('/')
72 file = path_parts.pop()76 file = path_parts.pop()
73 container = path_parts.pop()77 container = path_parts.pop()
74 except ValueError:78 except (ValueError, IndexError):
75 raise BackendException(79 raise BackendException(
76 "Expected four values to unpack in: swift:%s. "80 "Expected four values to unpack in: swift:%s. "
77 "Should have received something like: %s."81 "Should have received something like: %s."
@@ -80,5 +84,3 @@
80 authurl = "https://%s" % '/'.join(path_parts)84 authurl = "https://%s" % '/'.join(path_parts)
8185
82 return user, api_key, authurl, container, file86 return user, api_key, authurl, container, file
83
84
8587
=== modified file 'glance/teller/controllers.py'
--- glance/teller/controllers.py 2010-10-04 22:18:12 +0000
+++ glance/teller/controllers.py 2010-10-11 19:36:15 +0000
@@ -18,8 +18,11 @@
18Teller Image controller18Teller Image controller
19"""19"""
2020
21import logging
22
21import routes23import routes
22from webob import exc, Response24from webob import exc, Response
25
23from glance.common import wsgi26from glance.common import wsgi
24from glance.common import exception27from glance.common import exception
25from glance.parallax import db28from glance.parallax import db
@@ -50,7 +53,8 @@
5053
51 try:54 try:
52 image = registries.lookup_by_registry(registry, uri)55 image = registries.lookup_by_registry(registry, uri)
53 except registries.UnknownRegistryAdapter:56 logging.debug("Found image registry for URI: %s. Got: %s", uri, image)
57 except registries.UnknownImageRegistry:
54 return exc.HTTPBadRequest(body="Unknown registry '%s'" % registry,58 return exc.HTTPBadRequest(body="Unknown registry '%s'" % registry,
55 request=req,59 request=req,
56 content_type="text/plain")60 content_type="text/plain")
@@ -98,4 +102,3 @@
98 mapper.resource("image", "image", controller=ImageController(),102 mapper.resource("image", "image", controller=ImageController(),
99 collection={'detail': 'GET'})103 collection={'detail': 'GET'})
100 super(API, self).__init__(mapper)104 super(API, self).__init__(mapper)
101
102105
=== modified file 'glance/teller/registries.py'
--- glance/teller/registries.py 2010-10-04 22:18:12 +0000
+++ glance/teller/registries.py 2010-10-11 19:36:15 +0000
@@ -20,17 +20,17 @@
20import urlparse20import urlparse
2121
2222
23class RegistryAdapterException(Exception):23class ImageRegistryException(Exception):
24 """ Base class for all RegistryAdapter exceptions """24 """ Base class for all RegistryAdapter exceptions """
25 pass25 pass
2626
2727
28class UnknownRegistryAdapter(RegistryAdapterException):28class UnknownImageRegistry(ImageRegistryException):
29 """ Raised if we don't recognize the requested Registry protocol """29 """ Raised if we don't recognize the requested Registry protocol """
30 pass30 pass
3131
3232
33class RegistryAdapter(object):33class ImageRegistry(object):
34 """ Base class for all image endpoints """34 """ Base class for all image endpoints """
3535
36 @classmethod36 @classmethod
@@ -41,9 +41,9 @@
41 raise NotImplementedError41 raise NotImplementedError
4242
4343
44class ParallaxAdapter(RegistryAdapter):44class Parallax(ImageRegistry):
45 """45 """
46 ParallaxAdapter stuff46 Parallax stuff
47 """47 """
4848
49 @classmethod49 @classmethod
@@ -59,7 +59,7 @@
59 elif scheme == 'https':59 elif scheme == 'https':
60 conn_class = httplib.HTTPSConnection60 conn_class = httplib.HTTPSConnection
61 else:61 else:
62 raise RegistryAdapterException(62 raise ImageRegistryException(
63 "Unrecognized scheme '%s'" % scheme)63 "Unrecognized scheme '%s'" % scheme)
6464
65 conn = conn_class(parsed_uri.netloc)65 conn = conn_class(parsed_uri.netloc)
@@ -74,40 +74,24 @@
74 try:74 try:
75 return image_json["image"]75 return image_json["image"]
76 except KeyError:76 except KeyError:
77 raise RegistryAdapterException("Missing 'image' key")77 raise ImageRegistryException("Missing 'image' key")
78 except Exception: # gaierror
79 return None
78 finally:80 finally:
79 conn.close()81 conn.close()
8082
8183
82class FakeParallaxAdapter(ParallaxAdapter):
83 """
84 A Mock ParallaxAdapter returns a mocked response for any uri with
85 one or more 'success' and None for everything else.
86 """
87
88 @classmethod
89 def lookup(cls, parsed_uri):
90 if parsed_uri.netloc.count("success"):
91 # A successful attempt
92 files = [dict(location="teststr://chunk0", size=1235),
93 dict(location="teststr://chunk1", size=12345)]
94
95 return dict(files=files)
96
97
98REGISTRY_ADAPTERS = {84REGISTRY_ADAPTERS = {
99 'parallax': ParallaxAdapter,85 'parallax': Parallax
100 'fake_parallax': FakeParallaxAdapter
101}86}
10287
88
103def lookup_by_registry(registry, image_uri):89def lookup_by_registry(registry, image_uri):
104 """ Convenience function to lookup based on a registry protocol """90 """ Convenience function to lookup based on a registry protocol """
105 try:91 try:
106 adapter = REGISTRY_ADAPTERS[registry]92 adapter = REGISTRY_ADAPTERS[registry]
107 except KeyError:93 except KeyError:
108 raise UnknownRegistryAdapter("'%s' not found" % registry)94 raise UnknownImageRegistry("'%s' not found" % registry)
109 95
110 parsed_uri = urlparse.urlparse(image_uri)96 parsed_uri = urlparse.urlparse(image_uri)
111 return adapter.lookup(parsed_uri)97 return adapter.lookup(parsed_uri)
112
113
11498
=== added file 'run_tests.sh'
--- run_tests.sh 1970-01-01 00:00:00 +0000
+++ run_tests.sh 2010-10-11 19:36:15 +0000
@@ -0,0 +1,66 @@
1#!/bin/bash
2
3function usage {
4 echo "Usage: $0 [OPTION]..."
5 echo "Run Glance's test suite(s)"
6 echo ""
7 echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
8 echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
9 echo " -h, --help Print this usage message"
10 echo ""
11 echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
12 echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
13 echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
14 exit
15}
16
17function process_options {
18 array=$1
19 elements=${#array[@]}
20 for (( x=0;x<$elements;x++)); do
21 process_option ${array[${x}]}
22 done
23}
24
25function process_option {
26 option=$1
27 case $option in
28 -h|--help) usage;;
29 -V|--virtual-env) let always_venv=1; let never_venv=0;;
30 -N|--no-virtual-env) let always_venv=0; let never_venv=1;;
31 esac
32}
33
34venv=.glance-venv
35with_venv=tools/with_venv.sh
36always_venv=0
37never_venv=0
38options=("$@")
39
40process_options $options
41
42if [ $never_venv -eq 1 ]; then
43 # Just run the test suites in current environment
44 python run_tests.py
45 exit
46fi
47
48if [ -e ${venv} ]; then
49 ${with_venv} nosetests
50else
51 if [ $always_venv -eq 1 ]; then
52 # Automatically install the virtualenv
53 python tools/install_venv.py
54 else
55 echo -e "No virtual environment found...create one? (Y/n) \c"
56 read use_ve
57 if [ "x$use_ve" = "xY" ]; then
58 # Install the virtualenv and run the test suite in it
59 python tools/install_venv.py
60 else
61 nosetests
62 exit
63 fi
64 fi
65 ${with_venv} nosetests
66fi
067
=== added file 'tests/stubs.py'
--- tests/stubs.py 1970-01-01 00:00:00 +0000
+++ tests/stubs.py 2010-10-11 19:36:15 +0000
@@ -0,0 +1,149 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 OpenStack, LLC
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18"""Stubouts, mocks and fixtures for the test suite"""
19
20import httplib
21import StringIO
22
23import stubout
24
25import glance.teller.backends.swift
26
27def stub_out_http_backend(stubs):
28 """Stubs out the httplib.HTTPRequest.getresponse to return
29 faked-out data instead of grabbing actual contents of a resource
30
31 The stubbed getresponse() returns an iterator over
32 the data "I am a teapot, short and stout\n"
33
34 :param stubs: Set of stubout stubs
35
36 """
37
38 class FakeHTTPConnection(object):
39
40 DATA = 'I am a teapot, short and stout\n'
41
42 def getresponse(self):
43 return StringIO.StringIO(self.DATA)
44
45 def request(self, *_args, **_kwargs):
46 pass
47
48 fake_http_conn = FakeHTTPConnection()
49 stubs.Set(httplib.HTTPConnection, 'request',
50 fake_http_conn.request)
51 stubs.Set(httplib.HTTPSConnection, 'request',
52 fake_http_conn.request)
53 stubs.Set(httplib.HTTPConnection, 'getresponse',
54 fake_http_conn.getresponse)
55 stubs.Set(httplib.HTTPSConnection, 'getresponse',
56 fake_http_conn.getresponse)
57
58
59def stub_out_filesystem_backend(stubs):
60 """Stubs out the Filesystem Teller service to return fake
61 data from files.
62
63 The stubbed service always yields the following fixture::
64
65 //chunk0
66 //chunk1
67
68 :param stubs: Set of stubout stubs
69
70 """
71 class FakeFilesystemBackend(object):
72
73 @classmethod
74 def get(cls, parsed_uri, expected_size, conn_class=None):
75
76 return StringIO.StringIO(parsed_uri.path)
77
78 fake_filesystem_backend = FakeFilesystemBackend()
79 stubs.Set(glance.teller.backends.FilesystemBackend, 'get',
80 fake_filesystem_backend.get)
81
82
83def stub_out_swift_backend(stubs):
84 """Stubs out the Swift Teller backend with fake data
85 and calls.
86
87 The stubbed swift backend provides back an iterator over
88 the data "I am a teapot, short and stout\n"
89
90 :param stubs: Set of stubout stubs
91
92 """
93 class FakeSwiftAuth(object):
94 pass
95 class FakeSwiftConnection(object):
96 pass
97
98 class FakeSwiftBackend(object):
99
100 CHUNK_SIZE = 2
101 DATA = 'I am a teapot, short and stout\n'
102
103 @classmethod
104 def get(cls, parsed_uri, expected_size, conn_class=None):
105 SwiftBackend = glance.teller.backends.swift.SwiftBackend
106
107 # raise BackendException if URI is bad.
108 (user, api_key, authurl, container, file) = \
109 SwiftBackend.parse_swift_tokens(parsed_uri)
110
111 def chunk_it():
112 for i in xrange(0, len(cls.DATA), cls.CHUNK_SIZE):
113 yield cls.DATA[i:i+cls.CHUNK_SIZE]
114
115 return chunk_it()
116
117 fake_swift_backend = FakeSwiftBackend()
118 stubs.Set(glance.teller.backends.swift.SwiftBackend, 'get',
119 fake_swift_backend.get)
120
121
122def stub_out_parallax(stubs):
123 """Stubs out the Parallax registry with fake data returns.
124
125 The stubbed Parallax always returns the following fixture::
126
127 {'files': [
128 {'location': 'file:///chunk0', 'size': 12345},
129 {'location': 'file:///chunk1', 'size': 1235}
130 ]}
131
132 :param stubs: Set of stubout stubs
133
134 """
135 class FakeParallax(object):
136
137 DATA = \
138 {'files': [
139 {'location': 'file:///chunk0', 'size': 12345},
140 {'location': 'file:///chunk1', 'size': 1235}
141 ]}
142
143 @classmethod
144 def lookup(cls, _parsed_uri):
145 return cls.DATA
146
147 fake_parallax_registry = FakeParallax()
148 stubs.Set(glance.teller.registries.Parallax, 'lookup',
149 fake_parallax_registry.lookup)
0150
=== modified file 'tests/unit/test_teller_api.py'
--- tests/unit/test_teller_api.py 2010-10-01 23:07:46 +0000
+++ tests/unit/test_teller_api.py 2010-10-11 19:36:15 +0000
@@ -1,11 +1,38 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 OpenStack, LLC
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import stubout
1import unittest19import unittest
2from webob import Request, exc20from webob import Request, exc
21
3from glance.teller import controllers22from glance.teller import controllers
23from tests import stubs
24
425
5class TestImageController(unittest.TestCase):26class TestImageController(unittest.TestCase):
6 def setUp(self):27 def setUp(self):
28 """Establish a clean test environment"""
29 self.stubs = stubout.StubOutForTesting()
7 self.image_controller = controllers.ImageController()30 self.image_controller = controllers.ImageController()
831
32 def tearDown(self):
33 """Clear the test environment"""
34 self.stubs.UnsetAll()
35
9 def test_index_image_with_no_uri_should_raise_http_bad_request(self):36 def test_index_image_with_no_uri_should_raise_http_bad_request(self):
10 # uri must be specified37 # uri must be specified
11 request = Request.blank("/image")38 request = Request.blank("/image")
@@ -21,21 +48,18 @@
2148
22 def test_index_image_where_image_exists_should_return_the_data(self):49 def test_index_image_where_image_exists_should_return_the_data(self):
23 # FIXME: need urllib.quote here?50 # FIXME: need urllib.quote here?
24 image_uri = "http://parallax-success/myacct/my-image"51 stubs.stub_out_parallax(self.stubs)
52 stubs.stub_out_filesystem_backend(self.stubs)
53 image_uri = "http://parallax/myacct/my-image"
25 request = self._make_request(image_uri)54 request = self._make_request(image_uri)
26 response = self.image_controller.index(request)55 response = self.image_controller.index(request)
27 self.assertEqual("//chunk0//chunk1", response.body)56 self.assertEqual("/chunk0/chunk1", response.body)
2857
29 def test_index_image_where_image_doesnt_exist_should_raise_not_found(self):58 def test_index_image_where_image_doesnt_exist_should_raise_not_found(self):
30 image_uri = "http://parallax-failure/myacct/does-not-exist"59 image_uri = "http://bad-parallax-uri/myacct/does-not-exist"
31 request = self._make_request(image_uri)60 request = self._make_request(image_uri)
32 self.assertRaises(exc.HTTPNotFound, self.image_controller.index,61 self.assertRaises(exc.HTTPNotFound, self.image_controller.index,
33 request)62 request)
3463
35 def _make_request(self, image_uri, registry="fake_parallax"):64 def _make_request(self, image_uri, registry="parallax"):
36 return Request.blank(65 return Request.blank("/image?uri=%s&registry=%s" % (image_uri, registry))
37 "/image?uri=%s&registry=%s" % (image_uri, registry))
38
39
40if __name__ == "__main__":
41 unittest.main()
4266
=== modified file 'tests/unit/test_teller_backends.py'
--- tests/unit/test_teller_backends.py 2010-10-02 03:36:15 +0000
+++ tests/unit/test_teller_backends.py 2010-10-11 19:36:15 +0000
@@ -16,20 +16,29 @@
16# under the License.16# under the License.
1717
18from StringIO import StringIO18from StringIO import StringIO
19
20import stubout
19import unittest21import unittest
2022
21from cloudfiles import Connection23from glance.teller.backends.swift import SwiftBackend
22from cloudfiles.authentication import MockAuthentication as Auth
23
24from swiftfakehttp import CustomHTTPConnection
25from glance.teller.backends import Backend, BackendException, get_from_backend24from glance.teller.backends import Backend, BackendException, get_from_backend
2625from tests import stubs
2726
28class TestBackends(unittest.TestCase):27Backend.CHUNKSIZE = 2
28
29class TestBackend(unittest.TestCase):
29 def setUp(self):30 def setUp(self):
30 Backend.CHUNKSIZE = 231 """Establish a clean test environment"""
3132 self.stubs = stubout.StubOutForTesting()
32 def test_filesystem_get_from_backend(self):33
34 def tearDown(self):
35 """Clear the test environment"""
36 self.stubs.UnsetAll()
37
38
39class TestFilesystemBackend(TestBackend):
40
41 def test_get(self):
33 class FakeFile(object):42 class FakeFile(object):
34 def __enter__(self, *args, **kwargs):43 def __enter__(self, *args, **kwargs):
35 return StringIO('fakedata')44 return StringIO('fakedata')
@@ -43,65 +52,57 @@
43 chunks = [c for c in fetcher]52 chunks = [c for c in fetcher]
44 self.assertEqual(chunks, ["fa", "ke", "da", "ta"])53 self.assertEqual(chunks, ["fa", "ke", "da", "ta"])
4554
46 def test_http_get_from_backend(self):55
47 class FakeHTTPConnection(object):56class TestHTTPBackend(TestBackend):
48 def __init__(self, *args, **kwargs):57
49 pass58 def setUp(self):
50 def request(self, *args, **kwargs):59 super(TestHTTPBackend, self).setUp()
51 pass60 stubs.stub_out_http_backend(self.stubs)
52 def getresponse(self):61
53 return StringIO('fakedata')62 def test_http_get(self):
54 def close(self):63 url = "http://netloc/path/to/file.tar.gz"
55 pass64 expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
5665 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
57 fetcher = get_from_backend("http://netloc/path/to/file.tar.gz",66 fetcher = get_from_backend(url,
58 expected_size=8,67 expected_size=8)
59 conn_class=FakeHTTPConnection)68
6069 chunks = [c for c in fetcher]
61 chunks = [c for c in fetcher]70 self.assertEqual(chunks, expected_returns)
62 self.assertEqual(chunks, ["fa", "ke", "da", "ta"])71
6372 def test_https_get(self):
64 def test_swift_get_from_backend(self):73 url = "https://netloc/path/to/file.tar.gz"
65 class FakeSwift(object):74 expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
66 def __init__(self, *args, **kwargs): 75 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
67 pass76 fetcher = get_from_backend(url,
6877 expected_size=8)
69 @classmethod78
70 def get_connection(self, *args, **kwargs):79 chunks = [c for c in fetcher]
71 auth = Auth("user", "password")80 self.assertEqual(chunks, expected_returns)
72 conn = Connection(auth=auth)81
73 conn.connection = CustomHTTPConnection("localhost", 8000)82
74 return conn83class TestSwiftBackend(TestBackend):
84
85 def setUp(self):
86 super(TestSwiftBackend, self).setUp()
87 stubs.stub_out_swift_backend(self.stubs)
88
89 def test_get(self):
7590
76 swift_uri = "swift://user:password@localhost/container1/file.tar.gz"91 swift_uri = "swift://user:password@localhost/container1/file.tar.gz"
77 swift_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s', 92 expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
78 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']93 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
7994
80 fetcher = get_from_backend(swift_uri,95 fetcher = get_from_backend(swift_uri,
81 expected_size=21,96 expected_size=21,
82 conn_class=FakeSwift)97 conn_class=SwiftBackend)
8398
84 chunks = [c for c in fetcher]99 chunks = [c for c in fetcher]
85100
86 self.assertEqual(chunks, swift_returns)101 self.assertEqual(chunks, expected_returns)
87102
88 def test_swift_get_from_backend_with_bad_uri(self):103 def test_get_bad_uri(self):
89 class FakeSwift(object):
90 def __init__(self, *args, **kwargs):
91 pass
92
93 @classmethod
94 def get_connection(self, *args, **kwargs):
95 auth = Auth("user", "password")
96 conn = Connection(auth=auth)
97 conn.connection = CustomHTTPConnection("localhost", 8000)
98 return conn
99104
100 swift_url = "swift://localhost/container1/file.tar.gz"105 swift_url = "swift://localhost/container1/file.tar.gz"
101106
102 self.assertRaises(BackendException, get_from_backend, 107 self.assertRaises(BackendException, get_from_backend,
103 swift_url, expected_size=21)108 swift_url, expected_size=21)
104
105
106if __name__ == "__main__":
107 unittest.main()
108109
=== added file 'tests/utils.py'
--- tests/utils.py 1970-01-01 00:00:00 +0000
+++ tests/utils.py 2010-10-11 19:36:15 +0000
@@ -0,0 +1,27 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 OpenStack, LLC
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18"""Common utilities used in testing"""
19
20
21def is_cloudfiles_available():
22 """Returns True if Swift/Cloudfiles is importable"""
23 try:
24 import cloudfiles
25 return True
26 except ImportError:
27 return False
028
=== added directory 'tools'
=== added file 'tools/install_venv.py'
--- tools/install_venv.py 1970-01-01 00:00:00 +0000
+++ tools/install_venv.py 2010-10-11 19:36:15 +0000
@@ -0,0 +1,136 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 United States Government as represented by the
4# Administrator of the National Aeronautics and Space Administration.
5# All Rights Reserved.
6#
7# Copyright 2010 OpenStack, LLC
8#
9# Licensed under the Apache License, Version 2.0 (the "License"); you may
10# not use this file except in compliance with the License. You may obtain
11# a copy of the License at
12#
13# http://www.apache.org/licenses/LICENSE-2.0
14#
15# Unless required by applicable law or agreed to in writing, software
16# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
17# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
18# License for the specific language governing permissions and limitations
19# under the License.
20
21"""
22Installation script for Glance's development virtualenv
23"""
24
25import os
26import subprocess
27import sys
28
29
30ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
31VENV = os.path.join(ROOT, '.glance-venv')
32PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
33TWISTED_NOVA='http://nova.openstack.org/Twisted-10.0.0Nova.tar.gz'
34
35def die(message, *args):
36 print >>sys.stderr, message % args
37 sys.exit(1)
38
39
40def run_command(cmd, redirect_output=True, check_exit_code=True):
41 """
42 Runs a command in an out-of-process shell, returning the
43 output of that command. Working directory is ROOT.
44 """
45 if redirect_output:
46 stdout = subprocess.PIPE
47 else:
48 stdout = None
49
50 proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
51 output = proc.communicate()[0]
52 if check_exit_code and proc.returncode != 0:
53 die('Command "%s" failed.\n%s', ' '.join(cmd), output)
54 return output
55
56
57HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'], check_exit_code=False).strip())
58HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'], check_exit_code=False).strip())
59
60
61def check_dependencies():
62 """Make sure virtualenv is in the path."""
63
64 if not HAS_VIRTUALENV:
65 print 'not found.'
66 # Try installing it via easy_install...
67 if HAS_EASY_INSTALL:
68 print 'Installing virtualenv via easy_install...',
69 if not run_command(['which', 'easy_install']):
70 die('ERROR: virtualenv not found.\n\nGlance development requires virtualenv,'
71 ' please install it using your favorite package management tool')
72 print 'done.'
73 print 'done.'
74
75
76def create_virtualenv(venv=VENV):
77 """Creates the virtual environment and installs PIP only into the
78 virtual environment
79 """
80 print 'Creating venv...',
81 run_command(['virtualenv', '-q', '--no-site-packages', VENV])
82 print 'done.'
83 print 'Installing pip in virtualenv...',
84 if not run_command(['tools/with_venv.sh', 'easy_install', 'pip']).strip():
85 die("Failed to install pip.")
86 print 'done.'
87
88
89def install_dependencies(venv=VENV):
90 print 'Installing dependencies with pip (this can take a while)...'
91
92 # Install greenlet by hand - just listing it in the requires file does not
93 # get it in stalled in the right order
94 run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, 'greenlet'],
95 redirect_output=False)
96 run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES],
97 redirect_output=False)
98 run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, TWISTED_NOVA],
99 redirect_output=False)
100
101 # Tell the virtual env how to "import glance"
102 pthfile = os.path.join(venv, "lib", "python2.6", "site-packages", "glance.pth")
103 f = open(pthfile, 'w')
104 f.write("%s\n" % ROOT)
105
106
107def print_help():
108 help = """
109 Glance development environment setup is complete.
110
111 Glance development uses virtualenv to track and manage Python dependencies
112 while in development and testing.
113
114 To activate the Glance virtualenv for the extent of your current shell session
115 you can run:
116
117 $ source .glance-venv/bin/activate
118
119 Or, if you prefer, you can run commands in the virtualenv on a case by case
120 basis by running:
121
122 $ tools/with_venv.sh <your command>
123
124 Also, make test will automatically use the virtualenv.
125 """
126 print help
127
128
129def main(argv):
130 check_dependencies()
131 create_virtualenv()
132 install_dependencies()
133 print_help()
134
135if __name__ == '__main__':
136 main(sys.argv)
0137
=== added file 'tools/pip-requires'
--- tools/pip-requires 1970-01-01 00:00:00 +0000
+++ tools/pip-requires 2010-10-11 19:36:15 +0000
@@ -0,0 +1,16 @@
1greenlet>=0.3.1
2SQLAlchemy>=0.6.3
3pep8==0.5.0
4pylint==0.19
5anyjson
6eventlet>=0.9.12
7lockfile
8python-daemon==1.5.5
9python-gflags>=1.3
10routes
11webob
12wsgiref
13zope.interface
14nose
15mox==0.5.0
16-f http://pymox.googlecode.com/files/mox-0.5.0.tar.gz
017
=== added file 'tools/with_venv.sh'
--- tools/with_venv.sh 1970-01-01 00:00:00 +0000
+++ tools/with_venv.sh 2010-10-11 19:36:15 +0000
@@ -0,0 +1,4 @@
1#!/bin/bash
2TOOLS=`dirname $0`
3VENV=$TOOLS/../.glance-venv
4source $VENV/bin/activate && $@

Subscribers

People subscribed via source and target branches