Merge lp:~bac/charmworldlib/check-constraints into lp:charmworldlib/0.2
- check-constraints
- Merge into 0.2
Status: | Merged |
---|---|
Merge reported by: | Marco Ceppi |
Merged at revision: | not available |
Proposed branch: | lp:~bac/charmworldlib/check-constraints |
Merge into: | lp:charmworldlib/0.2 |
Diff against target: |
652 lines (+211/-370) 7 files modified
Makefile (+5/-1) charmworldlib/bundle.py (+1/-1) charmworldlib/utils.py (+76/-0) ez_setup.py (+0/-361) setup.py (+1/-6) tests/test_bundle.py (+4/-1) tests/test_utils.py (+124/-0) |
To merge this branch: | bzr merge lp:~bac/charmworldlib/check-constraints |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
charmers | Pending | ||
Review via email: mp+208669@code.launchpad.net |
Commit message
Description of the change
Add parse_constraints and check_constraints.
Also, removes the dependency on ez_setup since it caused the package to not
build and was unnecessary. There is a real system dependency on
python-virtualenv which install python-setuptools, so setuptools will always
be available and ez_setup not required.
Brad Crittenden (bac) wrote : | # |
- 33. By Brad Crittenden
-
Move constraint processing to utils.
- 34. By Brad Crittenden
-
Add new utils files
Brad Crittenden (bac) wrote : | # |
Please take a look.
- 35. By Brad Crittenden
-
Use forked version number for now.
Brad Crittenden (bac) wrote : | # |
Please take a look.
Richard Harding (rharding) wrote : | # |
LGTM thanks for the update and sanity checking.
https:/
File setup.py (right):
https:/
setup.py:13: version=
this makes constraint changes. Should it be 0.2.5-1~bac?
- 36. By Brad Crittenden
-
Update constraint parsing to be more robust.
Brad Crittenden (bac) wrote : | # |
Please take a look.
https:/
File setup.py (right):
https:/
setup.py:13: version=
On 2014/02/28 14:42:11, rharding wrote:
> this makes constraint changes. Should it be 0.2.5-1~bac?
Done.
Marco Ceppi (marcoceppi) wrote : | # |
Preview Diff
1 | === modified file 'Makefile' |
2 | --- Makefile 2014-02-04 15:21:40 +0000 |
3 | +++ Makefile 2014-03-04 13:42:34 +0000 |
4 | @@ -1,3 +1,4 @@ |
5 | +SYSDEPS = python-virtualenv |
6 | VENVS = .venv2 .venv3 |
7 | VENV2 = $(word 1, $(VENVS)) |
8 | VENV3 = $(word 2, $(VENVS)) |
9 | @@ -30,6 +31,9 @@ |
10 | |
11 | setup: $(VENVS) |
12 | |
13 | +sysdeps: |
14 | + sudo apt-get install --yes $(SYSDEPS) |
15 | + |
16 | test: setup |
17 | $(VENV3)/bin/nosetests -s --verbosity=2 |
18 | $(VENV2)/bin/nosetests -s --verbosity=2 --with-coverage --cover-package=charmworldlib |
19 | @@ -48,6 +52,6 @@ |
20 | clean-all: clean |
21 | rm -rf .pip-cache |
22 | |
23 | -.PHONY: setup test lint clean clean-all install install_2 install_3 |
24 | +.PHONY: setup sysdeps test lint clean clean-all install install_2 install_3 |
25 | |
26 | .DEFAULT_GOAL := all |
27 | |
28 | === modified file 'charmworldlib/bundle.py' |
29 | --- charmworldlib/bundle.py 2014-01-16 09:35:29 +0000 |
30 | +++ charmworldlib/bundle.py 2014-03-04 13:42:34 +0000 |
31 | @@ -1,5 +1,5 @@ |
32 | - |
33 | import yaml |
34 | + |
35 | from . import api |
36 | |
37 | |
38 | |
39 | === added file 'charmworldlib/utils.py' |
40 | --- charmworldlib/utils.py 1970-01-01 00:00:00 +0000 |
41 | +++ charmworldlib/utils.py 2014-03-04 13:42:34 +0000 |
42 | @@ -0,0 +1,76 @@ |
43 | +import collections |
44 | + |
45 | + |
46 | +# Define a sequence of allowed constraints to be used in the process of |
47 | +# preparing the bundle object. See the _prepare_constraints function below. |
48 | +ALLOWED_CONSTRAINTS = ( |
49 | + 'arch', |
50 | + 'container', |
51 | + 'cpu-cores', |
52 | + 'cpu-power', |
53 | + 'mem', |
54 | + 'root-disk', |
55 | + # XXX: BradCrittenden 2014-02-12: |
56 | + # tags are supported by MaaS only so they are not currently implemented. |
57 | + # It is unclear whether the GUI should support them or not so they are |
58 | + # being left out for now. |
59 | + # Also, tags are a comma-separated, which would clash with the currently |
60 | + # broken constraint parsing in the GUI. |
61 | + # 'tags', |
62 | +) |
63 | + |
64 | + |
65 | +def parse_constraints(original_constraints): |
66 | + """Parse the constraints and validate them. |
67 | + |
68 | + constraints is a string of key=value pairs or a dict. Due to |
69 | + historical reasons, many bundles use a comma-separated list rather |
70 | + than the space-separated list juju-core expects. This method |
71 | + handles both separators. |
72 | + |
73 | + Returns a dict of validated constraints. |
74 | + Raises ValueError if one or more constraints is invalid. |
75 | + """ |
76 | + |
77 | + constraints = original_constraints |
78 | + if not isinstance(constraints, collections.Mapping): |
79 | + constraints = constraints.strip() |
80 | + if not constraints: |
81 | + return {} |
82 | + #pairs = CONSTRAINTS_REGEX.findall(constraints) |
83 | + num_equals = constraints.count('=') |
84 | + # Comma separation is supported but deprecated. Attempt splitting on |
85 | + # it first as it yields better results if a mix of commas and spaces |
86 | + # is used. |
87 | + pairs = constraints.split(',') |
88 | + if num_equals != len(pairs): |
89 | + pairs = constraints.split(' ') |
90 | + if num_equals != len(pairs): |
91 | + raise ValueError('invalid constraints: {}'.format( |
92 | + original_constraints)) |
93 | + |
94 | + constraints = {} |
95 | + for item in pairs: |
96 | + k, v = item.split('=') |
97 | + if v.find(',') != -1: |
98 | + raise ValueError('invalid constraints: {}'.format( |
99 | + original_constraints)) |
100 | + |
101 | + constraints[k.strip()] = v.strip() |
102 | + |
103 | + unsupported = set(constraints).difference(ALLOWED_CONSTRAINTS) |
104 | + if unsupported: |
105 | + msg = 'unsupported constraints: {}'.format( |
106 | + ', '.join(sorted(unsupported))) |
107 | + raise ValueError(msg) |
108 | + return constraints |
109 | + |
110 | + |
111 | +def check_constraints(original_constraints): |
112 | + """Check to see that constraints are space-separated and valid.""" |
113 | + try: |
114 | + parsed = parse_constraints(original_constraints) |
115 | + except ValueError: |
116 | + return False |
117 | + tokens = original_constraints.strip().split() |
118 | + return len(parsed) == len(tokens) |
119 | |
120 | === removed file 'ez_setup.py' |
121 | --- ez_setup.py 2014-02-19 13:03:36 +0000 |
122 | +++ ez_setup.py 1970-01-01 00:00:00 +0000 |
123 | @@ -1,361 +0,0 @@ |
124 | -#!/usr/bin/env python |
125 | -"""Bootstrap setuptools installation |
126 | - |
127 | -To use setuptools in your package's setup.py, include this |
128 | -file in the same directory and add this to the top of your setup.py:: |
129 | - |
130 | - from ez_setup import use_setuptools |
131 | - use_setuptools() |
132 | - |
133 | -To require a specific version of setuptools, set a download |
134 | -mirror, or use an alternate download directory, simply supply |
135 | -the appropriate options to ``use_setuptools()``. |
136 | - |
137 | -This file can also be run as a script to install or upgrade setuptools. |
138 | -""" |
139 | -import os |
140 | -import shutil |
141 | -import sys |
142 | -import tempfile |
143 | -import tarfile |
144 | -import optparse |
145 | -import subprocess |
146 | -import platform |
147 | -import textwrap |
148 | - |
149 | -from distutils import log |
150 | - |
151 | -try: |
152 | - from site import USER_SITE |
153 | -except ImportError: |
154 | - USER_SITE = None |
155 | - |
156 | -DEFAULT_VERSION = "0.9.8" |
157 | -DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" |
158 | - |
159 | -def _python_cmd(*args): |
160 | - args = (sys.executable,) + args |
161 | - return subprocess.call(args) == 0 |
162 | - |
163 | -def _install(tarball, install_args=()): |
164 | - # extracting the tarball |
165 | - tmpdir = tempfile.mkdtemp() |
166 | - log.warn('Extracting in %s', tmpdir) |
167 | - old_wd = os.getcwd() |
168 | - try: |
169 | - os.chdir(tmpdir) |
170 | - tar = tarfile.open(tarball) |
171 | - _extractall(tar) |
172 | - tar.close() |
173 | - |
174 | - # going in the directory |
175 | - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) |
176 | - os.chdir(subdir) |
177 | - log.warn('Now working in %s', subdir) |
178 | - |
179 | - # installing |
180 | - log.warn('Installing Setuptools') |
181 | - if not _python_cmd('setup.py', 'install', *install_args): |
182 | - log.warn('Something went wrong during the installation.') |
183 | - log.warn('See the error message above.') |
184 | - # exitcode will be 2 |
185 | - return 2 |
186 | - finally: |
187 | - os.chdir(old_wd) |
188 | - shutil.rmtree(tmpdir) |
189 | - |
190 | - |
191 | -def _build_egg(egg, tarball, to_dir): |
192 | - # extracting the tarball |
193 | - tmpdir = tempfile.mkdtemp() |
194 | - log.warn('Extracting in %s', tmpdir) |
195 | - old_wd = os.getcwd() |
196 | - try: |
197 | - os.chdir(tmpdir) |
198 | - tar = tarfile.open(tarball) |
199 | - _extractall(tar) |
200 | - tar.close() |
201 | - |
202 | - # going in the directory |
203 | - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) |
204 | - os.chdir(subdir) |
205 | - log.warn('Now working in %s', subdir) |
206 | - |
207 | - # building an egg |
208 | - log.warn('Building a Setuptools egg in %s', to_dir) |
209 | - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) |
210 | - |
211 | - finally: |
212 | - os.chdir(old_wd) |
213 | - shutil.rmtree(tmpdir) |
214 | - # returning the result |
215 | - log.warn(egg) |
216 | - if not os.path.exists(egg): |
217 | - raise IOError('Could not build the egg.') |
218 | - |
219 | - |
220 | -def _do_download(version, download_base, to_dir, download_delay): |
221 | - egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' |
222 | - % (version, sys.version_info[0], sys.version_info[1])) |
223 | - if not os.path.exists(egg): |
224 | - tarball = download_setuptools(version, download_base, |
225 | - to_dir, download_delay) |
226 | - _build_egg(egg, tarball, to_dir) |
227 | - sys.path.insert(0, egg) |
228 | - |
229 | - # Remove previously-imported pkg_resources if present (see |
230 | - # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). |
231 | - if 'pkg_resources' in sys.modules: |
232 | - del sys.modules['pkg_resources'] |
233 | - |
234 | - import setuptools |
235 | - setuptools.bootstrap_install_from = egg |
236 | - |
237 | - |
238 | -def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, |
239 | - to_dir=os.curdir, download_delay=15): |
240 | - to_dir = os.path.abspath(to_dir) |
241 | - rep_modules = 'pkg_resources', 'setuptools' |
242 | - imported = set(sys.modules).intersection(rep_modules) |
243 | - try: |
244 | - import pkg_resources |
245 | - except ImportError: |
246 | - return _do_download(version, download_base, to_dir, download_delay) |
247 | - try: |
248 | - pkg_resources.require("setuptools>=" + version) |
249 | - return |
250 | - except pkg_resources.DistributionNotFound: |
251 | - return _do_download(version, download_base, to_dir, download_delay) |
252 | - except pkg_resources.VersionConflict as VC_err: |
253 | - if imported: |
254 | - msg = textwrap.dedent(""" |
255 | - The required version of setuptools (>={version}) is not available, |
256 | - and can't be installed while this script is running. Please |
257 | - install a more recent version first, using |
258 | - 'easy_install -U setuptools'. |
259 | - |
260 | - (Currently using {VC_err.args[0]!r}) |
261 | - """).format(VC_err=VC_err, version=version) |
262 | - sys.stderr.write(msg) |
263 | - sys.exit(2) |
264 | - |
265 | - # otherwise, reload ok |
266 | - del pkg_resources, sys.modules['pkg_resources'] |
267 | - return _do_download(version, download_base, to_dir, download_delay) |
268 | - |
269 | -def _clean_check(cmd, target): |
270 | - """ |
271 | - Run the command to download target. If the command fails, clean up before |
272 | - re-raising the error. |
273 | - """ |
274 | - try: |
275 | - subprocess.check_call(cmd) |
276 | - except subprocess.CalledProcessError: |
277 | - if os.access(target, os.F_OK): |
278 | - os.unlink(target) |
279 | - raise |
280 | - |
281 | -def download_file_powershell(url, target): |
282 | - """ |
283 | - Download the file at url to target using Powershell (which will validate |
284 | - trust). Raise an exception if the command cannot complete. |
285 | - """ |
286 | - target = os.path.abspath(target) |
287 | - cmd = [ |
288 | - 'powershell', |
289 | - '-Command', |
290 | - "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), |
291 | - ] |
292 | - _clean_check(cmd, target) |
293 | - |
294 | -def has_powershell(): |
295 | - if platform.system() != 'Windows': |
296 | - return False |
297 | - cmd = ['powershell', '-Command', 'echo test'] |
298 | - devnull = open(os.path.devnull, 'wb') |
299 | - try: |
300 | - try: |
301 | - subprocess.check_call(cmd, stdout=devnull, stderr=devnull) |
302 | - except: |
303 | - return False |
304 | - finally: |
305 | - devnull.close() |
306 | - return True |
307 | - |
308 | -download_file_powershell.viable = has_powershell |
309 | - |
310 | -def download_file_curl(url, target): |
311 | - cmd = ['curl', url, '--silent', '--output', target] |
312 | - _clean_check(cmd, target) |
313 | - |
314 | -def has_curl(): |
315 | - cmd = ['curl', '--version'] |
316 | - devnull = open(os.path.devnull, 'wb') |
317 | - try: |
318 | - try: |
319 | - subprocess.check_call(cmd, stdout=devnull, stderr=devnull) |
320 | - except: |
321 | - return False |
322 | - finally: |
323 | - devnull.close() |
324 | - return True |
325 | - |
326 | -download_file_curl.viable = has_curl |
327 | - |
328 | -def download_file_wget(url, target): |
329 | - cmd = ['wget', url, '--quiet', '--output-document', target] |
330 | - _clean_check(cmd, target) |
331 | - |
332 | -def has_wget(): |
333 | - cmd = ['wget', '--version'] |
334 | - devnull = open(os.path.devnull, 'wb') |
335 | - try: |
336 | - try: |
337 | - subprocess.check_call(cmd, stdout=devnull, stderr=devnull) |
338 | - except: |
339 | - return False |
340 | - finally: |
341 | - devnull.close() |
342 | - return True |
343 | - |
344 | -download_file_wget.viable = has_wget |
345 | - |
346 | -def download_file_insecure(url, target): |
347 | - """ |
348 | - Use Python to download the file, even though it cannot authenticate the |
349 | - connection. |
350 | - """ |
351 | - try: |
352 | - from urllib.request import urlopen |
353 | - except ImportError: |
354 | - from urllib2 import urlopen |
355 | - src = dst = None |
356 | - try: |
357 | - src = urlopen(url) |
358 | - # Read/write all in one block, so we don't create a corrupt file |
359 | - # if the download is interrupted. |
360 | - data = src.read() |
361 | - dst = open(target, "wb") |
362 | - dst.write(data) |
363 | - finally: |
364 | - if src: |
365 | - src.close() |
366 | - if dst: |
367 | - dst.close() |
368 | - |
369 | -download_file_insecure.viable = lambda: True |
370 | - |
371 | -def get_best_downloader(): |
372 | - downloaders = [ |
373 | - download_file_powershell, |
374 | - download_file_curl, |
375 | - download_file_wget, |
376 | - download_file_insecure, |
377 | - ] |
378 | - |
379 | - for dl in downloaders: |
380 | - if dl.viable(): |
381 | - return dl |
382 | - |
383 | -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, |
384 | - to_dir=os.curdir, delay=15, |
385 | - downloader_factory=get_best_downloader): |
386 | - """Download setuptools from a specified location and return its filename |
387 | - |
388 | - `version` should be a valid setuptools version number that is available |
389 | - as an egg for download under the `download_base` URL (which should end |
390 | - with a '/'). `to_dir` is the directory where the egg will be downloaded. |
391 | - `delay` is the number of seconds to pause before an actual download |
392 | - attempt. |
393 | - |
394 | - ``downloader_factory`` should be a function taking no arguments and |
395 | - returning a function for downloading a URL to a target. |
396 | - """ |
397 | - # making sure we use the absolute path |
398 | - to_dir = os.path.abspath(to_dir) |
399 | - tgz_name = "setuptools-%s.tar.gz" % version |
400 | - url = download_base + tgz_name |
401 | - saveto = os.path.join(to_dir, tgz_name) |
402 | - if not os.path.exists(saveto): # Avoid repeated downloads |
403 | - log.warn("Downloading %s", url) |
404 | - downloader = downloader_factory() |
405 | - downloader(url, saveto) |
406 | - return os.path.realpath(saveto) |
407 | - |
408 | - |
409 | -def _extractall(self, path=".", members=None): |
410 | - """Extract all members from the archive to the current working |
411 | - directory and set owner, modification time and permissions on |
412 | - directories afterwards. `path' specifies a different directory |
413 | - to extract to. `members' is optional and must be a subset of the |
414 | - list returned by getmembers(). |
415 | - """ |
416 | - import copy |
417 | - import operator |
418 | - from tarfile import ExtractError |
419 | - directories = [] |
420 | - |
421 | - if members is None: |
422 | - members = self |
423 | - |
424 | - for tarinfo in members: |
425 | - if tarinfo.isdir(): |
426 | - # Extract directories with a safe mode. |
427 | - directories.append(tarinfo) |
428 | - tarinfo = copy.copy(tarinfo) |
429 | - tarinfo.mode = 448 # decimal for oct 0700 |
430 | - self.extract(tarinfo, path) |
431 | - |
432 | - # Reverse sort directories. |
433 | - directories.sort(key=operator.attrgetter('name'), reverse=True) |
434 | - |
435 | - # Set correct owner, mtime and filemode on directories. |
436 | - for tarinfo in directories: |
437 | - dirpath = os.path.join(path, tarinfo.name) |
438 | - try: |
439 | - self.chown(tarinfo, dirpath) |
440 | - self.utime(tarinfo, dirpath) |
441 | - self.chmod(tarinfo, dirpath) |
442 | - except ExtractError as e: |
443 | - if self.errorlevel > 1: |
444 | - raise |
445 | - else: |
446 | - self._dbg(1, "tarfile: %s" % e) |
447 | - |
448 | - |
449 | -def _build_install_args(options): |
450 | - """ |
451 | - Build the arguments to 'python setup.py install' on the setuptools package |
452 | - """ |
453 | - return ['--user'] if options.user_install else [] |
454 | - |
455 | -def _parse_args(): |
456 | - """ |
457 | - Parse the command line for options |
458 | - """ |
459 | - parser = optparse.OptionParser() |
460 | - parser.add_option( |
461 | - '--user', dest='user_install', action='store_true', default=False, |
462 | - help='install in user site package (requires Python 2.6 or later)') |
463 | - parser.add_option( |
464 | - '--download-base', dest='download_base', metavar="URL", |
465 | - default=DEFAULT_URL, |
466 | - help='alternative URL from where to download the setuptools package') |
467 | - parser.add_option( |
468 | - '--insecure', dest='downloader_factory', action='store_const', |
469 | - const=lambda: download_file_insecure, default=get_best_downloader, |
470 | - help='Use internal, non-validating downloader' |
471 | - ) |
472 | - options, args = parser.parse_args() |
473 | - # positional arguments are ignored |
474 | - return options |
475 | - |
476 | -def main(version=DEFAULT_VERSION): |
477 | - """Install or upgrade setuptools and EasyInstall""" |
478 | - options = _parse_args() |
479 | - tarball = download_setuptools(download_base=options.download_base, |
480 | - downloader_factory=options.downloader_factory) |
481 | - return _install(tarball, _build_install_args(options)) |
482 | - |
483 | -if __name__ == '__main__': |
484 | - sys.exit(main()) |
485 | |
486 | === modified file 'setup.py' |
487 | --- setup.py 2014-02-19 13:04:04 +0000 |
488 | +++ setup.py 2014-03-04 13:42:34 +0000 |
489 | @@ -3,17 +3,12 @@ |
490 | # Copyright 2013 Marco Ceppi. This software is licensed under the |
491 | # GNU General Public License version 3 (see the file LICENSE). |
492 | |
493 | -import ez_setup |
494 | - |
495 | - |
496 | -ez_setup.use_setuptools() |
497 | - |
498 | from setuptools import setup |
499 | |
500 | |
501 | setup( |
502 | name='charmworldlib', |
503 | - version="0.2.4-1", |
504 | + version="0.2.5-1", |
505 | packages=['charmworldlib'], |
506 | maintainer='Marco Ceppi', |
507 | maintainer_email='marco@ceppi.net', |
508 | |
509 | === modified file 'tests/test_bundle.py' |
510 | --- tests/test_bundle.py 2014-01-16 09:35:29 +0000 |
511 | +++ tests/test_bundle.py 2014-03-04 13:42:34 +0000 |
512 | @@ -3,7 +3,10 @@ |
513 | import unittest |
514 | |
515 | from mock import patch |
516 | -from charmworldlib.bundle import Bundles, Bundle |
517 | +from charmworldlib.bundle import ( |
518 | + Bundle, |
519 | + Bundles, |
520 | +) |
521 | |
522 | |
523 | class BundlesTest(unittest.TestCase): |
524 | |
525 | === added file 'tests/test_utils.py' |
526 | --- tests/test_utils.py 1970-01-01 00:00:00 +0000 |
527 | +++ tests/test_utils.py 2014-03-04 13:42:34 +0000 |
528 | @@ -0,0 +1,124 @@ |
529 | +"""Unit tests for utils.""" |
530 | + |
531 | +import unittest |
532 | + |
533 | +from charmworldlib.utils import ( |
534 | + check_constraints, |
535 | + parse_constraints, |
536 | +) |
537 | + |
538 | + |
539 | +class TestParseConstraints(unittest.TestCase): |
540 | + |
541 | + def test_valid_constraints(self): |
542 | + # Valid constraints are returned as they are. |
543 | + constraints = { |
544 | + 'arch': 'i386', |
545 | + 'cpu-cores': 4, |
546 | + 'cpu-power': 2, |
547 | + 'mem': 2000, |
548 | + 'root-disk': '1G', |
549 | + 'container': 'lxc', |
550 | + } |
551 | + self.assertEqual(constraints, parse_constraints(constraints)) |
552 | + |
553 | + def test_valid_constraints_subset(self): |
554 | + # A subset of valid constraints is returned as it is. |
555 | + constraints = {'cpu-cores': '4', 'cpu-power': 2} |
556 | + self.assertEqual(constraints, parse_constraints(constraints)) |
557 | + |
558 | + def test_invalid_constraints(self): |
559 | + # A ValueError is raised if unsupported constraints are found. |
560 | + with self.assertRaises(ValueError) as context_manager: |
561 | + parse_constraints({'arch': 'i386', 'not-valid': 'bang!'}) |
562 | + self.assertEqual( |
563 | + 'unsupported constraints: not-valid', |
564 | + str(context_manager.exception)) |
565 | + |
566 | + def test_string_constraints_space_separated(self): |
567 | + # String constraints are converted to a dict. |
568 | + constraints = 'arch=i386 cpu-cores=4 cpu-power=2 mem=2000' |
569 | + expected = { |
570 | + 'arch': 'i386', |
571 | + 'cpu-cores': '4', |
572 | + 'cpu-power': '2', |
573 | + 'mem': '2000', |
574 | + } |
575 | + self.assertEqual(expected, parse_constraints(constraints)) |
576 | + |
577 | + def test_string_constraints_comma_separated(self): |
578 | + # String constraints are converted to a dict. |
579 | + constraints = 'arch=i386,cpu-cores=4,cpu-power=2,mem=2000' |
580 | + expected = { |
581 | + 'arch': 'i386', |
582 | + 'cpu-cores': '4', |
583 | + 'cpu-power': '2', |
584 | + 'mem': '2000', |
585 | + } |
586 | + self.assertEqual(expected, parse_constraints(constraints)) |
587 | + |
588 | + def test_string_constraints_mixed_separated(self): |
589 | + # String constraints are converted to a dict. |
590 | + constraints = 'arch=i386, cpu-cores=4, cpu-power=2,mem=2000' |
591 | + expected = { |
592 | + 'arch': 'i386', |
593 | + 'cpu-cores': '4', |
594 | + 'cpu-power': '2', |
595 | + 'mem': '2000', |
596 | + } |
597 | + self.assertEqual(expected, parse_constraints(constraints)) |
598 | + |
599 | + def test_unsupported_string_constraints(self): |
600 | + # A ValueError is raised if unsupported string constraints are found. |
601 | + with self.assertRaises(ValueError) as context_manager: |
602 | + parse_constraints('cpu-cores=4 invalid1=1 invalid2=2') |
603 | + self.assertEqual( |
604 | + 'unsupported constraints: invalid1, invalid2', |
605 | + str(context_manager.exception)) |
606 | + |
607 | + def test_invalid_string_constraints(self): |
608 | + # A ValueError is raised if an invalid string is passed. |
609 | + with self.assertRaises(ValueError) as context_manager: |
610 | + parse_constraints('arch=,cpu-cores=,') |
611 | + self.assertEqual( |
612 | + 'invalid constraints: arch=,cpu-cores=,', |
613 | + str(context_manager.exception)) |
614 | + |
615 | + def test_invalid_no_pairs(self): |
616 | + # A ValueError is raised if an invalid string is passed. |
617 | + with self.assertRaises(ValueError) as context_manager: |
618 | + parse_constraints('yo') |
619 | + self.assertEqual( |
620 | + 'invalid constraints: yo', |
621 | + str(context_manager.exception)) |
622 | + |
623 | + def test_not_key_value_pairs_constraints(self): |
624 | + # A ValueError is raised if a string doesn't have the same number of |
625 | + # tokens as '=' characters. |
626 | + with self.assertRaises(ValueError) as context_manager: |
627 | + parse_constraints('arch=1,cpu-cores') |
628 | + self.assertEqual( |
629 | + 'invalid constraints: arch=1,cpu-cores', |
630 | + str(context_manager.exception)) |
631 | + |
632 | + def test_empty_constraints_return_empty_dict(self): |
633 | + constraints = parse_constraints('') |
634 | + self.assertEqual({}, constraints) |
635 | + |
636 | + |
637 | +class TestCheckConstraints(unittest.TestCase): |
638 | + |
639 | + def test_space_separated(self): |
640 | + constraints = 'cpu-cores=4 mem=2000 root-disk=1' |
641 | + result = check_constraints(constraints) |
642 | + self.assertTrue(result) |
643 | + |
644 | + def test_comma_separated(self): |
645 | + constraints = 'cpu-cores=4,mem=2000,root-disk=1' |
646 | + result = check_constraints(constraints) |
647 | + self.assertFalse(result) |
648 | + |
649 | + def test_invalid_separated(self): |
650 | + constraints = 'invalid=4,mem=2000,root-disk=1' |
651 | + result = check_constraints(constraints) |
652 | + self.assertFalse(result) |
Reviewers: mp+208669_ code.launchpad. net,
Message:
Please take a look.
Description:
Add parse_constraints and check_constraints.
Also, removes the dependency on ez_setup since it caused the package to
not
build and was unnecessary. There is a real system dependency on
python-virtualenv which install python-setuptools, so setuptools will
always
be available and ez_setup not required.
https:/ /code.launchpad .net/~bac/ charmworldlib/ check-constrain ts/+merge/ 208669
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/69430043/
Affected files (+166, -370 lines): bundle. py bundle. py
M Makefile
A [revision details]
M charmworldlib/
D ez_setup.py
M setup.py
M tests/test_