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
1=== modified file '.bzrignore'
2--- .bzrignore 2010-09-27 22:43:04 +0000
3+++ .bzrignore 2010-10-11 19:36:15 +0000
4@@ -1,2 +1,3 @@
5 *.pyc
6 glance.egg-info
7+glance.sqlite
8
9=== modified file 'glance/parallax/controllers.py'
10--- glance/parallax/controllers.py 2010-10-04 22:18:12 +0000
11+++ glance/parallax/controllers.py 2010-10-11 19:36:15 +0000
12@@ -97,11 +97,3 @@
13 mapper.resource("image", "images", controller=ImageController(),
14 collection={'detail': 'GET'})
15 super(API, self).__init__(mapper)
16-
17-
18-
19-
20-
21-
22-
23-
24
25=== modified file 'glance/teller/backends/__init__.py'
26--- glance/teller/backends/__init__.py 2010-10-04 22:18:12 +0000
27+++ glance/teller/backends/__init__.py 2010-10-11 19:36:15 +0000
28@@ -39,20 +39,9 @@
29 CHUNKSIZE = 4096
30
31
32-class TestStrBackend(Backend):
33- """ Backend used for testing """
34-
35- @classmethod
36- def get(cls, parsed_uri, expected_size):
37- """
38- teststr://data
39- """
40- yield parsed_uri.path
41-
42-
43 class FilesystemBackend(Backend):
44 @classmethod
45- def get(cls, parsed_uri, expected_size, opener=lambda p: open(p, "b")):
46+ def get(cls, parsed_uri, expected_size, opener=lambda p: open(p, "rb")):
47 """ Filesystem-based backend
48
49 file:///path/to/file.tar.gz.0
50@@ -72,8 +61,7 @@
51 "file": FilesystemBackend,
52 "http": HTTPBackend,
53 "https": HTTPBackend,
54- "swift": SwiftBackend,
55- "teststr": TestStrBackend
56+ "swift": SwiftBackend
57 }
58
59 parsed_uri = urlparse.urlparse(uri)
60@@ -85,4 +73,3 @@
61 raise UnsupportedBackend("No backend found for '%s'" % scheme)
62
63 return backend.get(parsed_uri, **kwargs)
64-
65
66=== modified file 'glance/teller/backends/http.py'
67--- glance/teller/backends/http.py 2010-10-04 22:18:12 +0000
68+++ glance/teller/backends/http.py 2010-10-11 19:36:15 +0000
69@@ -43,5 +43,3 @@
70 return backends._file_iter(conn.getresponse(), cls.CHUNKSIZE)
71 finally:
72 conn.close()
73-
74-
75
76=== modified file 'glance/teller/backends/swift.py'
77--- glance/teller/backends/swift.py 2010-10-04 22:18:12 +0000
78+++ glance/teller/backends/swift.py 2010-10-11 19:36:15 +0000
79@@ -15,7 +15,6 @@
80 # License for the specific language governing permissions and limitations
81 # under the License.
82
83-import cloudfiles
84 from glance.teller.backends import Backend, BackendException
85
86
87@@ -39,6 +38,10 @@
88 if conn_class:
89 pass # Use the provided conn_class
90 else:
91+ # Import cloudfiles here because stubout will replace this call
92+ # with a faked swift client in the unittests, avoiding import
93+ # errors if the test system does not have cloudfiles installed
94+ import cloudfiles
95 conn_class = cloudfiles
96
97 swift_conn = conn_class.get_connection(username=user, api_key=api_key,
98@@ -64,14 +67,15 @@
99 3) reassemble authurl
100 """
101 path = parsed_uri.path.lstrip('//')
102+ netloc = parsed_uri.netloc
103
104 try:
105- creds, path = path.split('@')
106+ creds, netloc = netloc.split('@')
107 user, api_key = creds.split(':')
108 path_parts = path.split('/')
109 file = path_parts.pop()
110 container = path_parts.pop()
111- except ValueError:
112+ except (ValueError, IndexError):
113 raise BackendException(
114 "Expected four values to unpack in: swift:%s. "
115 "Should have received something like: %s."
116@@ -80,5 +84,3 @@
117 authurl = "https://%s" % '/'.join(path_parts)
118
119 return user, api_key, authurl, container, file
120-
121-
122
123=== modified file 'glance/teller/controllers.py'
124--- glance/teller/controllers.py 2010-10-04 22:18:12 +0000
125+++ glance/teller/controllers.py 2010-10-11 19:36:15 +0000
126@@ -18,8 +18,11 @@
127 Teller Image controller
128 """
129
130+import logging
131+
132 import routes
133 from webob import exc, Response
134+
135 from glance.common import wsgi
136 from glance.common import exception
137 from glance.parallax import db
138@@ -50,7 +53,8 @@
139
140 try:
141 image = registries.lookup_by_registry(registry, uri)
142- except registries.UnknownRegistryAdapter:
143+ logging.debug("Found image registry for URI: %s. Got: %s", uri, image)
144+ except registries.UnknownImageRegistry:
145 return exc.HTTPBadRequest(body="Unknown registry '%s'" % registry,
146 request=req,
147 content_type="text/plain")
148@@ -98,4 +102,3 @@
149 mapper.resource("image", "image", controller=ImageController(),
150 collection={'detail': 'GET'})
151 super(API, self).__init__(mapper)
152-
153
154=== modified file 'glance/teller/registries.py'
155--- glance/teller/registries.py 2010-10-04 22:18:12 +0000
156+++ glance/teller/registries.py 2010-10-11 19:36:15 +0000
157@@ -20,17 +20,17 @@
158 import urlparse
159
160
161-class RegistryAdapterException(Exception):
162+class ImageRegistryException(Exception):
163 """ Base class for all RegistryAdapter exceptions """
164 pass
165
166
167-class UnknownRegistryAdapter(RegistryAdapterException):
168+class UnknownImageRegistry(ImageRegistryException):
169 """ Raised if we don't recognize the requested Registry protocol """
170 pass
171
172
173-class RegistryAdapter(object):
174+class ImageRegistry(object):
175 """ Base class for all image endpoints """
176
177 @classmethod
178@@ -41,9 +41,9 @@
179 raise NotImplementedError
180
181
182-class ParallaxAdapter(RegistryAdapter):
183+class Parallax(ImageRegistry):
184 """
185- ParallaxAdapter stuff
186+ Parallax stuff
187 """
188
189 @classmethod
190@@ -59,7 +59,7 @@
191 elif scheme == 'https':
192 conn_class = httplib.HTTPSConnection
193 else:
194- raise RegistryAdapterException(
195+ raise ImageRegistryException(
196 "Unrecognized scheme '%s'" % scheme)
197
198 conn = conn_class(parsed_uri.netloc)
199@@ -74,40 +74,24 @@
200 try:
201 return image_json["image"]
202 except KeyError:
203- raise RegistryAdapterException("Missing 'image' key")
204+ raise ImageRegistryException("Missing 'image' key")
205+ except Exception: # gaierror
206+ return None
207 finally:
208 conn.close()
209
210
211-class FakeParallaxAdapter(ParallaxAdapter):
212- """
213- A Mock ParallaxAdapter returns a mocked response for any uri with
214- one or more 'success' and None for everything else.
215- """
216-
217- @classmethod
218- def lookup(cls, parsed_uri):
219- if parsed_uri.netloc.count("success"):
220- # A successful attempt
221- files = [dict(location="teststr://chunk0", size=1235),
222- dict(location="teststr://chunk1", size=12345)]
223-
224- return dict(files=files)
225-
226-
227 REGISTRY_ADAPTERS = {
228- 'parallax': ParallaxAdapter,
229- 'fake_parallax': FakeParallaxAdapter
230+ 'parallax': Parallax
231 }
232
233+
234 def lookup_by_registry(registry, image_uri):
235 """ Convenience function to lookup based on a registry protocol """
236 try:
237 adapter = REGISTRY_ADAPTERS[registry]
238 except KeyError:
239- raise UnknownRegistryAdapter("'%s' not found" % registry)
240+ raise UnknownImageRegistry("'%s' not found" % registry)
241
242 parsed_uri = urlparse.urlparse(image_uri)
243 return adapter.lookup(parsed_uri)
244-
245-
246
247=== added file 'run_tests.sh'
248--- run_tests.sh 1970-01-01 00:00:00 +0000
249+++ run_tests.sh 2010-10-11 19:36:15 +0000
250@@ -0,0 +1,66 @@
251+#!/bin/bash
252+
253+function usage {
254+ echo "Usage: $0 [OPTION]..."
255+ echo "Run Glance's test suite(s)"
256+ echo ""
257+ echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
258+ echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
259+ echo " -h, --help Print this usage message"
260+ echo ""
261+ echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
262+ echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
263+ echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
264+ exit
265+}
266+
267+function process_options {
268+ array=$1
269+ elements=${#array[@]}
270+ for (( x=0;x<$elements;x++)); do
271+ process_option ${array[${x}]}
272+ done
273+}
274+
275+function process_option {
276+ option=$1
277+ case $option in
278+ -h|--help) usage;;
279+ -V|--virtual-env) let always_venv=1; let never_venv=0;;
280+ -N|--no-virtual-env) let always_venv=0; let never_venv=1;;
281+ esac
282+}
283+
284+venv=.glance-venv
285+with_venv=tools/with_venv.sh
286+always_venv=0
287+never_venv=0
288+options=("$@")
289+
290+process_options $options
291+
292+if [ $never_venv -eq 1 ]; then
293+ # Just run the test suites in current environment
294+ python run_tests.py
295+ exit
296+fi
297+
298+if [ -e ${venv} ]; then
299+ ${with_venv} nosetests
300+else
301+ if [ $always_venv -eq 1 ]; then
302+ # Automatically install the virtualenv
303+ python tools/install_venv.py
304+ else
305+ echo -e "No virtual environment found...create one? (Y/n) \c"
306+ read use_ve
307+ if [ "x$use_ve" = "xY" ]; then
308+ # Install the virtualenv and run the test suite in it
309+ python tools/install_venv.py
310+ else
311+ nosetests
312+ exit
313+ fi
314+ fi
315+ ${with_venv} nosetests
316+fi
317
318=== added file 'tests/stubs.py'
319--- tests/stubs.py 1970-01-01 00:00:00 +0000
320+++ tests/stubs.py 2010-10-11 19:36:15 +0000
321@@ -0,0 +1,149 @@
322+# vim: tabstop=4 shiftwidth=4 softtabstop=4
323+
324+# Copyright 2010 OpenStack, LLC
325+# All Rights Reserved.
326+#
327+# Licensed under the Apache License, Version 2.0 (the "License"); you may
328+# not use this file except in compliance with the License. You may obtain
329+# a copy of the License at
330+#
331+# http://www.apache.org/licenses/LICENSE-2.0
332+#
333+# Unless required by applicable law or agreed to in writing, software
334+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
335+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
336+# License for the specific language governing permissions and limitations
337+# under the License.
338+
339+"""Stubouts, mocks and fixtures for the test suite"""
340+
341+import httplib
342+import StringIO
343+
344+import stubout
345+
346+import glance.teller.backends.swift
347+
348+def stub_out_http_backend(stubs):
349+ """Stubs out the httplib.HTTPRequest.getresponse to return
350+ faked-out data instead of grabbing actual contents of a resource
351+
352+ The stubbed getresponse() returns an iterator over
353+ the data "I am a teapot, short and stout\n"
354+
355+ :param stubs: Set of stubout stubs
356+
357+ """
358+
359+ class FakeHTTPConnection(object):
360+
361+ DATA = 'I am a teapot, short and stout\n'
362+
363+ def getresponse(self):
364+ return StringIO.StringIO(self.DATA)
365+
366+ def request(self, *_args, **_kwargs):
367+ pass
368+
369+ fake_http_conn = FakeHTTPConnection()
370+ stubs.Set(httplib.HTTPConnection, 'request',
371+ fake_http_conn.request)
372+ stubs.Set(httplib.HTTPSConnection, 'request',
373+ fake_http_conn.request)
374+ stubs.Set(httplib.HTTPConnection, 'getresponse',
375+ fake_http_conn.getresponse)
376+ stubs.Set(httplib.HTTPSConnection, 'getresponse',
377+ fake_http_conn.getresponse)
378+
379+
380+def stub_out_filesystem_backend(stubs):
381+ """Stubs out the Filesystem Teller service to return fake
382+ data from files.
383+
384+ The stubbed service always yields the following fixture::
385+
386+ //chunk0
387+ //chunk1
388+
389+ :param stubs: Set of stubout stubs
390+
391+ """
392+ class FakeFilesystemBackend(object):
393+
394+ @classmethod
395+ def get(cls, parsed_uri, expected_size, conn_class=None):
396+
397+ return StringIO.StringIO(parsed_uri.path)
398+
399+ fake_filesystem_backend = FakeFilesystemBackend()
400+ stubs.Set(glance.teller.backends.FilesystemBackend, 'get',
401+ fake_filesystem_backend.get)
402+
403+
404+def stub_out_swift_backend(stubs):
405+ """Stubs out the Swift Teller backend with fake data
406+ and calls.
407+
408+ The stubbed swift backend provides back an iterator over
409+ the data "I am a teapot, short and stout\n"
410+
411+ :param stubs: Set of stubout stubs
412+
413+ """
414+ class FakeSwiftAuth(object):
415+ pass
416+ class FakeSwiftConnection(object):
417+ pass
418+
419+ class FakeSwiftBackend(object):
420+
421+ CHUNK_SIZE = 2
422+ DATA = 'I am a teapot, short and stout\n'
423+
424+ @classmethod
425+ def get(cls, parsed_uri, expected_size, conn_class=None):
426+ SwiftBackend = glance.teller.backends.swift.SwiftBackend
427+
428+ # raise BackendException if URI is bad.
429+ (user, api_key, authurl, container, file) = \
430+ SwiftBackend.parse_swift_tokens(parsed_uri)
431+
432+ def chunk_it():
433+ for i in xrange(0, len(cls.DATA), cls.CHUNK_SIZE):
434+ yield cls.DATA[i:i+cls.CHUNK_SIZE]
435+
436+ return chunk_it()
437+
438+ fake_swift_backend = FakeSwiftBackend()
439+ stubs.Set(glance.teller.backends.swift.SwiftBackend, 'get',
440+ fake_swift_backend.get)
441+
442+
443+def stub_out_parallax(stubs):
444+ """Stubs out the Parallax registry with fake data returns.
445+
446+ The stubbed Parallax always returns the following fixture::
447+
448+ {'files': [
449+ {'location': 'file:///chunk0', 'size': 12345},
450+ {'location': 'file:///chunk1', 'size': 1235}
451+ ]}
452+
453+ :param stubs: Set of stubout stubs
454+
455+ """
456+ class FakeParallax(object):
457+
458+ DATA = \
459+ {'files': [
460+ {'location': 'file:///chunk0', 'size': 12345},
461+ {'location': 'file:///chunk1', 'size': 1235}
462+ ]}
463+
464+ @classmethod
465+ def lookup(cls, _parsed_uri):
466+ return cls.DATA
467+
468+ fake_parallax_registry = FakeParallax()
469+ stubs.Set(glance.teller.registries.Parallax, 'lookup',
470+ fake_parallax_registry.lookup)
471
472=== modified file 'tests/unit/test_teller_api.py'
473--- tests/unit/test_teller_api.py 2010-10-01 23:07:46 +0000
474+++ tests/unit/test_teller_api.py 2010-10-11 19:36:15 +0000
475@@ -1,11 +1,38 @@
476+# vim: tabstop=4 shiftwidth=4 softtabstop=4
477+
478+# Copyright 2010 OpenStack, LLC
479+# All Rights Reserved.
480+#
481+# Licensed under the Apache License, Version 2.0 (the "License"); you may
482+# not use this file except in compliance with the License. You may obtain
483+# a copy of the License at
484+#
485+# http://www.apache.org/licenses/LICENSE-2.0
486+#
487+# Unless required by applicable law or agreed to in writing, software
488+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
489+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
490+# License for the specific language governing permissions and limitations
491+# under the License.
492+
493+import stubout
494 import unittest
495 from webob import Request, exc
496+
497 from glance.teller import controllers
498+from tests import stubs
499+
500
501 class TestImageController(unittest.TestCase):
502 def setUp(self):
503+ """Establish a clean test environment"""
504+ self.stubs = stubout.StubOutForTesting()
505 self.image_controller = controllers.ImageController()
506
507+ def tearDown(self):
508+ """Clear the test environment"""
509+ self.stubs.UnsetAll()
510+
511 def test_index_image_with_no_uri_should_raise_http_bad_request(self):
512 # uri must be specified
513 request = Request.blank("/image")
514@@ -21,21 +48,18 @@
515
516 def test_index_image_where_image_exists_should_return_the_data(self):
517 # FIXME: need urllib.quote here?
518- image_uri = "http://parallax-success/myacct/my-image"
519+ stubs.stub_out_parallax(self.stubs)
520+ stubs.stub_out_filesystem_backend(self.stubs)
521+ image_uri = "http://parallax/myacct/my-image"
522 request = self._make_request(image_uri)
523 response = self.image_controller.index(request)
524- self.assertEqual("//chunk0//chunk1", response.body)
525+ self.assertEqual("/chunk0/chunk1", response.body)
526
527 def test_index_image_where_image_doesnt_exist_should_raise_not_found(self):
528- image_uri = "http://parallax-failure/myacct/does-not-exist"
529+ image_uri = "http://bad-parallax-uri/myacct/does-not-exist"
530 request = self._make_request(image_uri)
531 self.assertRaises(exc.HTTPNotFound, self.image_controller.index,
532 request)
533
534- def _make_request(self, image_uri, registry="fake_parallax"):
535- return Request.blank(
536- "/image?uri=%s&registry=%s" % (image_uri, registry))
537-
538-
539-if __name__ == "__main__":
540- unittest.main()
541+ def _make_request(self, image_uri, registry="parallax"):
542+ return Request.blank("/image?uri=%s&registry=%s" % (image_uri, registry))
543
544=== modified file 'tests/unit/test_teller_backends.py'
545--- tests/unit/test_teller_backends.py 2010-10-02 03:36:15 +0000
546+++ tests/unit/test_teller_backends.py 2010-10-11 19:36:15 +0000
547@@ -16,20 +16,29 @@
548 # under the License.
549
550 from StringIO import StringIO
551+
552+import stubout
553 import unittest
554
555-from cloudfiles import Connection
556-from cloudfiles.authentication import MockAuthentication as Auth
557-
558-from swiftfakehttp import CustomHTTPConnection
559+from glance.teller.backends.swift import SwiftBackend
560 from glance.teller.backends import Backend, BackendException, get_from_backend
561-
562-
563-class TestBackends(unittest.TestCase):
564+from tests import stubs
565+
566+Backend.CHUNKSIZE = 2
567+
568+class TestBackend(unittest.TestCase):
569 def setUp(self):
570- Backend.CHUNKSIZE = 2
571-
572- def test_filesystem_get_from_backend(self):
573+ """Establish a clean test environment"""
574+ self.stubs = stubout.StubOutForTesting()
575+
576+ def tearDown(self):
577+ """Clear the test environment"""
578+ self.stubs.UnsetAll()
579+
580+
581+class TestFilesystemBackend(TestBackend):
582+
583+ def test_get(self):
584 class FakeFile(object):
585 def __enter__(self, *args, **kwargs):
586 return StringIO('fakedata')
587@@ -43,65 +52,57 @@
588 chunks = [c for c in fetcher]
589 self.assertEqual(chunks, ["fa", "ke", "da", "ta"])
590
591- def test_http_get_from_backend(self):
592- class FakeHTTPConnection(object):
593- def __init__(self, *args, **kwargs):
594- pass
595- def request(self, *args, **kwargs):
596- pass
597- def getresponse(self):
598- return StringIO('fakedata')
599- def close(self):
600- pass
601-
602- fetcher = get_from_backend("http://netloc/path/to/file.tar.gz",
603- expected_size=8,
604- conn_class=FakeHTTPConnection)
605-
606- chunks = [c for c in fetcher]
607- self.assertEqual(chunks, ["fa", "ke", "da", "ta"])
608-
609- def test_swift_get_from_backend(self):
610- class FakeSwift(object):
611- def __init__(self, *args, **kwargs):
612- pass
613-
614- @classmethod
615- def get_connection(self, *args, **kwargs):
616- auth = Auth("user", "password")
617- conn = Connection(auth=auth)
618- conn.connection = CustomHTTPConnection("localhost", 8000)
619- return conn
620+
621+class TestHTTPBackend(TestBackend):
622+
623+ def setUp(self):
624+ super(TestHTTPBackend, self).setUp()
625+ stubs.stub_out_http_backend(self.stubs)
626+
627+ def test_http_get(self):
628+ url = "http://netloc/path/to/file.tar.gz"
629+ expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
630+ 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
631+ fetcher = get_from_backend(url,
632+ expected_size=8)
633+
634+ chunks = [c for c in fetcher]
635+ self.assertEqual(chunks, expected_returns)
636+
637+ def test_https_get(self):
638+ url = "https://netloc/path/to/file.tar.gz"
639+ expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
640+ 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
641+ fetcher = get_from_backend(url,
642+ expected_size=8)
643+
644+ chunks = [c for c in fetcher]
645+ self.assertEqual(chunks, expected_returns)
646+
647+
648+class TestSwiftBackend(TestBackend):
649+
650+ def setUp(self):
651+ super(TestSwiftBackend, self).setUp()
652+ stubs.stub_out_swift_backend(self.stubs)
653+
654+ def test_get(self):
655
656 swift_uri = "swift://user:password@localhost/container1/file.tar.gz"
657- swift_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
658- 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
659+ expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
660+ 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
661
662 fetcher = get_from_backend(swift_uri,
663 expected_size=21,
664- conn_class=FakeSwift)
665+ conn_class=SwiftBackend)
666
667 chunks = [c for c in fetcher]
668
669- self.assertEqual(chunks, swift_returns)
670-
671- def test_swift_get_from_backend_with_bad_uri(self):
672- class FakeSwift(object):
673- def __init__(self, *args, **kwargs):
674- pass
675-
676- @classmethod
677- def get_connection(self, *args, **kwargs):
678- auth = Auth("user", "password")
679- conn = Connection(auth=auth)
680- conn.connection = CustomHTTPConnection("localhost", 8000)
681- return conn
682+ self.assertEqual(chunks, expected_returns)
683+
684+ def test_get_bad_uri(self):
685
686 swift_url = "swift://localhost/container1/file.tar.gz"
687
688 self.assertRaises(BackendException, get_from_backend,
689 swift_url, expected_size=21)
690-
691-
692-if __name__ == "__main__":
693- unittest.main()
694
695=== added file 'tests/utils.py'
696--- tests/utils.py 1970-01-01 00:00:00 +0000
697+++ tests/utils.py 2010-10-11 19:36:15 +0000
698@@ -0,0 +1,27 @@
699+# vim: tabstop=4 shiftwidth=4 softtabstop=4
700+
701+# Copyright 2010 OpenStack, LLC
702+# All Rights Reserved.
703+#
704+# Licensed under the Apache License, Version 2.0 (the "License"); you may
705+# not use this file except in compliance with the License. You may obtain
706+# a copy of the License at
707+#
708+# http://www.apache.org/licenses/LICENSE-2.0
709+#
710+# Unless required by applicable law or agreed to in writing, software
711+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
712+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
713+# License for the specific language governing permissions and limitations
714+# under the License.
715+
716+"""Common utilities used in testing"""
717+
718+
719+def is_cloudfiles_available():
720+ """Returns True if Swift/Cloudfiles is importable"""
721+ try:
722+ import cloudfiles
723+ return True
724+ except ImportError:
725+ return False
726
727=== added directory 'tools'
728=== added file 'tools/install_venv.py'
729--- tools/install_venv.py 1970-01-01 00:00:00 +0000
730+++ tools/install_venv.py 2010-10-11 19:36:15 +0000
731@@ -0,0 +1,136 @@
732+# vim: tabstop=4 shiftwidth=4 softtabstop=4
733+
734+# Copyright 2010 United States Government as represented by the
735+# Administrator of the National Aeronautics and Space Administration.
736+# All Rights Reserved.
737+#
738+# Copyright 2010 OpenStack, LLC
739+#
740+# Licensed under the Apache License, Version 2.0 (the "License"); you may
741+# not use this file except in compliance with the License. You may obtain
742+# a copy of the License at
743+#
744+# http://www.apache.org/licenses/LICENSE-2.0
745+#
746+# Unless required by applicable law or agreed to in writing, software
747+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
748+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
749+# License for the specific language governing permissions and limitations
750+# under the License.
751+
752+"""
753+Installation script for Glance's development virtualenv
754+"""
755+
756+import os
757+import subprocess
758+import sys
759+
760+
761+ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
762+VENV = os.path.join(ROOT, '.glance-venv')
763+PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
764+TWISTED_NOVA='http://nova.openstack.org/Twisted-10.0.0Nova.tar.gz'
765+
766+def die(message, *args):
767+ print >>sys.stderr, message % args
768+ sys.exit(1)
769+
770+
771+def run_command(cmd, redirect_output=True, check_exit_code=True):
772+ """
773+ Runs a command in an out-of-process shell, returning the
774+ output of that command. Working directory is ROOT.
775+ """
776+ if redirect_output:
777+ stdout = subprocess.PIPE
778+ else:
779+ stdout = None
780+
781+ proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
782+ output = proc.communicate()[0]
783+ if check_exit_code and proc.returncode != 0:
784+ die('Command "%s" failed.\n%s', ' '.join(cmd), output)
785+ return output
786+
787+
788+HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'], check_exit_code=False).strip())
789+HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'], check_exit_code=False).strip())
790+
791+
792+def check_dependencies():
793+ """Make sure virtualenv is in the path."""
794+
795+ if not HAS_VIRTUALENV:
796+ print 'not found.'
797+ # Try installing it via easy_install...
798+ if HAS_EASY_INSTALL:
799+ print 'Installing virtualenv via easy_install...',
800+ if not run_command(['which', 'easy_install']):
801+ die('ERROR: virtualenv not found.\n\nGlance development requires virtualenv,'
802+ ' please install it using your favorite package management tool')
803+ print 'done.'
804+ print 'done.'
805+
806+
807+def create_virtualenv(venv=VENV):
808+ """Creates the virtual environment and installs PIP only into the
809+ virtual environment
810+ """
811+ print 'Creating venv...',
812+ run_command(['virtualenv', '-q', '--no-site-packages', VENV])
813+ print 'done.'
814+ print 'Installing pip in virtualenv...',
815+ if not run_command(['tools/with_venv.sh', 'easy_install', 'pip']).strip():
816+ die("Failed to install pip.")
817+ print 'done.'
818+
819+
820+def install_dependencies(venv=VENV):
821+ print 'Installing dependencies with pip (this can take a while)...'
822+
823+ # Install greenlet by hand - just listing it in the requires file does not
824+ # get it in stalled in the right order
825+ run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, 'greenlet'],
826+ redirect_output=False)
827+ run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES],
828+ redirect_output=False)
829+ run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, TWISTED_NOVA],
830+ redirect_output=False)
831+
832+ # Tell the virtual env how to "import glance"
833+ pthfile = os.path.join(venv, "lib", "python2.6", "site-packages", "glance.pth")
834+ f = open(pthfile, 'w')
835+ f.write("%s\n" % ROOT)
836+
837+
838+def print_help():
839+ help = """
840+ Glance development environment setup is complete.
841+
842+ Glance development uses virtualenv to track and manage Python dependencies
843+ while in development and testing.
844+
845+ To activate the Glance virtualenv for the extent of your current shell session
846+ you can run:
847+
848+ $ source .glance-venv/bin/activate
849+
850+ Or, if you prefer, you can run commands in the virtualenv on a case by case
851+ basis by running:
852+
853+ $ tools/with_venv.sh <your command>
854+
855+ Also, make test will automatically use the virtualenv.
856+ """
857+ print help
858+
859+
860+def main(argv):
861+ check_dependencies()
862+ create_virtualenv()
863+ install_dependencies()
864+ print_help()
865+
866+if __name__ == '__main__':
867+ main(sys.argv)
868
869=== added file 'tools/pip-requires'
870--- tools/pip-requires 1970-01-01 00:00:00 +0000
871+++ tools/pip-requires 2010-10-11 19:36:15 +0000
872@@ -0,0 +1,16 @@
873+greenlet>=0.3.1
874+SQLAlchemy>=0.6.3
875+pep8==0.5.0
876+pylint==0.19
877+anyjson
878+eventlet>=0.9.12
879+lockfile
880+python-daemon==1.5.5
881+python-gflags>=1.3
882+routes
883+webob
884+wsgiref
885+zope.interface
886+nose
887+mox==0.5.0
888+-f http://pymox.googlecode.com/files/mox-0.5.0.tar.gz
889
890=== added file 'tools/with_venv.sh'
891--- tools/with_venv.sh 1970-01-01 00:00:00 +0000
892+++ tools/with_venv.sh 2010-10-11 19:36:15 +0000
893@@ -0,0 +1,4 @@
894+#!/bin/bash
895+TOOLS=`dirname $0`
896+VENV=$TOOLS/../.glance-venv
897+source $VENV/bin/activate && $@

Subscribers

People subscribed via source and target branches