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