Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@sugarlabs.org>2014-02-21 12:33:37 (GMT)
committer Aleksey Lim <alsroot@sugarlabs.org>2014-02-21 12:33:37 (GMT)
commitd48da5dbad4f47d2aa6403ba13a8ca72fce5297f (patch)
tree3033b28564f8818bd648e8b455a1a65d76d6fc8b
parent4a1136a6e50294aad1eb0c38c4344c88f2ff72c2 (diff)
Implement context solver on node level
-rw-r--r--sugar_network/model/__init__.py44
-rw-r--r--sugar_network/model/context.py2
-rw-r--r--sugar_network/node/model.py164
-rw-r--r--sugar_network/node/obs.py3
-rw-r--r--sugar_network/node/routes.py49
-rw-r--r--sugar_network/toolkit/sat.py12
-rw-r--r--tests/__init__.py2
-rwxr-xr-xtests/units/model/context.py32
-rwxr-xr-xtests/units/model/model.py51
-rwxr-xr-xtests/units/node/model.py520
-rwxr-xr-xtests/units/node/node.py540
-rwxr-xr-xtests/units/toolkit/sat.py29
12 files changed, 1042 insertions, 406 deletions
diff --git a/sugar_network/model/__init__.py b/sugar_network/model/__init__.py
index 7278d10..6858957 100644
--- a/sugar_network/model/__init__.py
+++ b/sugar_network/model/__init__.py
@@ -141,6 +141,7 @@ def load_bundle(blob, context=None, initial=False, extra_deps=None):
release_notes = None
release = {}
blob_meta = {}
+ version = None
try:
bundle = Bundle(blob.path, mime_type='application/zip')
@@ -148,7 +149,7 @@ def load_bundle(blob, context=None, initial=False, extra_deps=None):
context_type = 'book'
if not context:
context = this.request['context']
- release['version'] = this.request['version']
+ version = this.request['version']
if 'license' in this.request:
release['license'] = this.request['license']
if isinstance(release['license'], basestring):
@@ -180,29 +181,21 @@ def load_bundle(blob, context=None, initial=False, extra_deps=None):
if extra_deps:
spec.requires.update(parse_requires(extra_deps))
- release['version'] = spec['version']
+ version = spec['version']
release['stability'] = spec['stability']
if spec['license'] is not EMPTY_LICENSE:
release['license'] = spec['license']
- release['requires'] = requires = []
- for dep_name, dep in spec.requires.items():
- found = False
- for version in dep.versions_range():
- requires.append('%s-%s' % (dep_name, version))
- found = True
- if not found:
- requires.append(dep_name)
+ release['commands'] = spec.commands
+ release['requires'] = spec.requires
release['spec'] = {'*-*': {
'bundle': blob.digest,
- 'commands': spec.commands,
- 'requires': spec.requires,
}}
release['unpack_size'] = unpack_size
blob_meta['mime_type'] = 'application/vnd.olpc-sugar'
enforce(context, http.BadRequest, 'Context is not specified')
- enforce(release['version'], http.BadRequest, 'Version is not specified')
- release['release'] = parse_version(release['version'])
+ enforce(version, http.BadRequest, 'Version is not specified')
+ release['version'] = parse_version(version)
if initial and not contexts.exists(context):
enforce(context_meta, http.BadRequest, 'No way to initate context')
context_meta['guid'] = context
@@ -211,21 +204,22 @@ def load_bundle(blob, context=None, initial=False, extra_deps=None):
else:
enforce(context_type in contexts[context]['type'],
http.BadRequest, 'Inappropriate bundle type')
- context_obj = contexts[context]
+ context_doc = contexts[context]
- releases = context_obj['releases']
if 'license' not in release:
+ releases = context_doc['releases'].values()
enforce(releases, http.BadRequest, 'License is not specified')
- recent = max(releases, key=lambda x: releases[x]['release'])
- release['license'] = releases[recent]['license']
+ recent = max(releases, key=lambda x: x.get('value', {}).get('release'))
+ enforce(recent, http.BadRequest, 'License is not specified')
+ release['license'] = recent['value']['license']
_logger.debug('Load %r release: %r', context, release)
- if this.request.principal in context_obj['author']:
- diff = context_obj.patch(context_meta)
+ if this.request.principal in context_doc['author']:
+ diff = context_doc.patch(context_meta)
if diff:
this.call(method='PUT', path=['context', context], content=diff)
- context_obj.props.update(diff)
+ context_doc.props.update(diff)
# TRANS: Release notes title
title = i18n._('%(name)s %(version)s release')
else:
@@ -236,15 +230,15 @@ def load_bundle(blob, context=None, initial=False, extra_deps=None):
'context': context,
'type': 'notification',
'title': i18n.encode(title,
- name=context_obj['title'],
- version=release['version'],
+ name=context_doc['title'],
+ version=version,
),
'message': release_notes or '',
},
content_type='application/json')
- filename = ''.join(i18n.decode(context_obj['title']).split())
- blob_meta['name'] = '%s-%s' % (filename, release['version'])
+ filename = ''.join(i18n.decode(context_doc['title']).split())
+ blob_meta['name'] = '%s-%s' % (filename, version)
files.update(blob.digest, blob_meta)
return context, release
diff --git a/sugar_network/model/context.py b/sugar_network/model/context.py
index 6bac120..951aad1 100644
--- a/sugar_network/model/context.py
+++ b/sugar_network/model/context.py
@@ -89,7 +89,7 @@ class Context(db.Resource):
def rating(self, value):
return value
- @db.stored_property(db.List, default=[], acl=ACL.PUBLIC | ACL.LOCAL)
+ @db.stored_property(default='', acl=ACL.PUBLIC | ACL.LOCAL)
def dependencies(self, value):
"""Software dependencies.
diff --git a/sugar_network/node/model.py b/sugar_network/node/model.py
index 2681b2d..7276b75 100644
--- a/sugar_network/node/model.py
+++ b/sugar_network/node/model.py
@@ -13,14 +13,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import bisect
import logging
from sugar_network import db, toolkit
-from sugar_network.model import Release, context
+from sugar_network.model import Release, context as base_context
from sugar_network.node import obs
from sugar_network.toolkit.router import ACL
from sugar_network.toolkit.coroutine import this
-from sugar_network.toolkit import http, coroutine, enforce
+from sugar_network.toolkit import spec, sat, http, coroutine, enforce
_logger = logging.getLogger('node.model')
@@ -48,7 +49,7 @@ class _Release(Release):
lsb_id = distro
lsb_release = None
releases = this.resource.record.get('releases')
- statuses = releases['value'].setdefault('status', {})
+ resolves = releases['value'].setdefault('resolves', {})
to_presolve = []
for repo in obs.get_repos():
@@ -60,21 +61,26 @@ class _Release(Release):
not lsb_release and repo['name'] in releases['value']:
continue
pkgs = sum([value.get(i, []) for i in ('binary', 'devel')], [])
+ version = None
try:
for arch in repo['arches']:
- obs.resolve(repo['name'], arch, pkgs)
+ version = obs.resolve(repo['name'], arch, pkgs)['version']
except Exception, error:
_logger.warning('Failed to resolve %r on %s',
pkgs, repo['name'])
- status = str(error)
+ resolve = {'status': str(error)}
else:
to_presolve.append((repo['name'], pkgs))
- status = 'success'
- statuses[repo['name']] = status
+ resolve = {
+ 'version': spec.parse_version(version),
+ 'packages': pkgs,
+ 'status': 'success',
+ }
+ resolves.setdefault(repo['name'], {}).update(resolve)
if to_presolve and _presolve_queue is not None:
_presolve_queue.put(to_presolve)
- if statuses:
+ if resolves:
this.resource.record.set('releases', **releases)
return value
@@ -85,7 +91,7 @@ class _Release(Release):
# TODO Delete presolved files
-class Context(context.Context):
+class Context(base_context.Context):
@db.stored_property(db.Aggregated, subtype=_Release(),
acl=ACL.READ | ACL.INSERT | ACL.REMOVE | ACL.REPLACE)
@@ -143,15 +149,10 @@ def merge(volume, records):
for record in records:
resource_ = record.get('resource')
if resource_:
- resource = resource_
directory = volume[resource_]
continue
if 'guid' in record:
- guid = record['guid']
- existed = directory.exists(guid)
- if existed:
- layer = directory.get(guid)['layer']
seqno, merged = directory.merge(**record)
synced = synced or merged
if seqno is not None:
@@ -169,9 +170,144 @@ def merge(volume, records):
return commit_seq, merged_seq
+def solve(volume, top_context, lsb_id=None, lsb_release=None,
+ stability=None, requires=None):
+ top_context = volume['context'][top_context]
+ top_stability = stability or ['stable']
+ if isinstance(top_stability, basestring):
+ top_stability = [top_stability]
+ top_cond = None
+ top_requires = {}
+ if isinstance(requires, basestring):
+ top_requires.update(spec.parse_requires(requires))
+ elif requires:
+ for i in requires:
+ top_requires.update(spec.parse_requires(i))
+ if top_context['dependencies']:
+ top_requires.update(spec.parse_requires(top_context['dependencies']))
+ if top_context.guid in top_requires:
+ top_cond = top_requires.pop(top_context.guid)
+
+ lsb_distro = '-'.join([lsb_id, lsb_release]) if lsb_release else None
+ varset = [None]
+ context_clauses = {}
+ clauses = []
+
+ _logger.debug('Solve %r lsb_id=%r lsb_release=%r stability=%r requires=%r',
+ top_context.guid, lsb_id, lsb_release, top_stability, top_requires)
+
+ def ensure_version(version, cond):
+ if not cond:
+ return True
+ for not_before, before in cond['restrictions']:
+ if before is not None and version >= before or \
+ not_before is not None and version < not_before:
+ return False
+ return True
+
+ def rate_release(digest, release):
+ return [_STABILITY_RATES.get(release['stability']) or 0,
+ release['version'],
+ digest,
+ ]
+
+ def add_deps(context, v_usage, deps):
+ if top_requires and context.guid == top_context.guid:
+ deps.update(top_requires)
+ for dep, cond in deps.items():
+ dep_clause = [-v_usage]
+ for v_release in add_context(dep):
+ if ensure_version(varset[v_release][0], cond):
+ dep_clause.append(v_release)
+ clauses.append(dep_clause)
+
+ def add_context(context):
+ if context in context_clauses:
+ return context_clauses[context]
+ context = volume['context'][context]
+ releases = context['releases']
+ clause = []
+
+ if 'package' in context['type']:
+ pkg_lst = None
+ pkg_ver = []
+ pkg = releases.get('resolves', {}).get(lsb_distro)
+ if pkg:
+ pkg_ver = pkg['version']
+ pkg_lst = pkg['packages']
+ else:
+ alias = releases.get(lsb_id) or releases.get('*')
+ if alias:
+ alias = alias['value']
+ pkg_lst = alias.get('binary', []) + alias.get('devel', [])
+ if pkg_lst:
+ clause.append(len(varset))
+ varset.append((pkg_ver, 'packages', {context.guid: pkg_lst}))
+ else:
+ candidates = []
+ for digest, release in releases.items():
+ if 'value' not in release:
+ continue
+ release = release['value']
+ if release['stability'] not in top_stability or \
+ context.guid == top_context.guid and \
+ not ensure_version(release['version'], top_cond):
+ continue
+ bisect.insort(candidates, rate_release(digest, release))
+ for release in reversed(candidates):
+ digest = release[-1]
+ release = releases[digest]['value']
+ v_release = len(varset)
+ varset.append((
+ release['version'],
+ 'files',
+ {context.guid: digest},
+ ))
+ clause.append(v_release)
+ add_deps(context, v_release, release.get('requires') or {})
+
+ if clause:
+ context_clauses[context.guid] = clause
+ else:
+ _logger.trace('No candidates for %r', context.guid)
+ return clause
+
+ top_clause = add_context(top_context.guid)
+ if not top_clause:
+ _logger.debug('No versions for %r', top_context.guid)
+ return None
+ result = sat.solve(clauses + [top_clause], context_clauses)
+ if not result:
+ _logger.debug('Failed to solve %r', top_context.guid)
+ return None
+ if not top_context.guid in result:
+ _logger.debug('No top versions for %r', top_context.guid)
+ return None
+
+ solution = {'files': {}, 'packages': {}}
+ for v in result.values():
+ __, key, items = varset[v]
+ solution[key].update(items)
+ top_release = top_context['releases'][solution['files'][top_context.guid]]
+ solution['commands'] = top_release['value']['commands']
+
+ _logger.debug('Solution for %r: %r', top_context.guid, solution)
+
+ return solution
+
+
def presolve(presolve_path):
global _presolve_queue
_presolve_queue = coroutine.Queue()
for repo_name, pkgs in _presolve_queue:
obs.presolve(repo_name, pkgs, presolve_path)
+
+
+_STABILITY_RATES = {
+ 'insecure': 0,
+ 'buggy': 1,
+ 'developer': 2,
+ 'testing': 3,
+ 'stable': 4,
+ }
diff --git a/sugar_network/node/obs.py b/sugar_network/node/obs.py
index 6ef9e55..796ea7c 100644
--- a/sugar_network/node/obs.py
+++ b/sugar_network/node/obs.py
@@ -46,12 +46,13 @@ def get_repos():
def resolve(repo, arch, packages):
- _request('GET', ['resolve'], params={
+ response = _request('GET', ['resolve'], params={
'project': obs_project.value,
'repository': repo,
'arch': arch,
'package': packages,
})
+ return dict(response.find('binary').items())
def presolve(repo_name, packages, dst_path):
diff --git a/sugar_network/node/routes.py b/sugar_network/node/routes.py
index 6323cbc..da6c675 100644
--- a/sugar_network/node/routes.py
+++ b/sugar_network/node/routes.py
@@ -20,9 +20,10 @@ import hashlib
from ConfigParser import ConfigParser
from os.path import join, isdir, exists
-from sugar_network import db, node, toolkit, model
+from sugar_network import db, node, toolkit
from sugar_network.db import files
-from sugar_network.node import stats_user
+from sugar_network.model import FrontRoutes, load_bundle
+from sugar_network.node import stats_user, model
# pylint: disable-msg=W0611
from sugar_network.toolkit.router import route, preroute, postroute, ACL
from sugar_network.toolkit.router import Unauthorized, Request, fallbackroute
@@ -39,11 +40,11 @@ _AUTH_POOL_SIZE = 1024
_logger = logging.getLogger('node.routes')
-class NodeRoutes(db.Routes, model.FrontRoutes):
+class NodeRoutes(db.Routes, FrontRoutes):
def __init__(self, guid, **kwargs):
db.Routes.__init__(self, **kwargs)
- model.FrontRoutes.__init__(self)
+ FrontRoutes.__init__(self)
self._guid = guid
self._auth_pool = pylru.lrucache(_AUTH_POOL_SIZE)
self._auth_config = None
@@ -120,7 +121,7 @@ class NodeRoutes(db.Routes, model.FrontRoutes):
def submit_release(self, request, initial):
blob = files.post(request.content_stream)
try:
- context, release = model.load_bundle(blob, initial=initial)
+ context, release = load_bundle(blob, initial=initial)
except Exception:
files.delete(blob.digest)
raise
@@ -144,40 +145,24 @@ class NodeRoutes(db.Routes, model.FrontRoutes):
layer = list(set(doc['layer']) - set(request.content))
directory.update(request.guid, {'layer': layer})
+ @route('GET', ['context', None], cmd='solve',
+ arguments={'requires': list, 'stability': list},
+ mime_type='application/json')
+ def solve(self, request):
+ solution = model.solve(self.volume, request.guid, **request)
+ enforce(solution is not None, 'Failed to solve')
+ return solution
+
@route('GET', ['context', None], cmd='clone',
arguments={'requires': list})
def get_clone(self, request, response):
- deps = {}
- if 'requires' in request:
- for i in request['requires']:
- deps.update(parse_requires(i))
- version = request.get('version')
- if version:
- version = parse_version(version)[0]
- stability = request.get('stability') or 'stable'
-
- recent = None
- context = self.volume['context'][request.guid]
- for release in context['releases'].values():
- release = release.get('value')
- if not release:
- continue
- spec = release['spec']['*-*']
- if version and version != release['release'][0] or \
- stability and stability != release['stability'] or \
- deps and not ensure_requires(spec['requires'], deps):
- continue
- if recent is None or release['release'] > recent['release']:
- recent = release
- enforce(recent, http.NotFound, 'No releases found')
-
- response.meta = recent
- return files.get(recent['spec']['*-*']['bundle'])
+ response.meta = self.solve(request)
+ return files.get(response.meta['files'][request.guid])
@route('HEAD', ['context', None], cmd='clone',
arguments={'requires': list})
def head_clone(self, request, response):
- self.get_clone(request, response)
+ response.meta = self.solve(request)
@route('GET', ['user', None], cmd='stats-info',
mime_type='application/json', acl=ACL.AUTH)
diff --git a/sugar_network/toolkit/sat.py b/sugar_network/toolkit/sat.py
index a908dd7..65afdaf 100644
--- a/sugar_network/toolkit/sat.py
+++ b/sugar_network/toolkit/sat.py
@@ -35,7 +35,7 @@ from sugar_network.toolkit import enforce
_logger = logging.getLogger('sat')
-def solve(clauses, at_most_one_clauses, decide):
+def solve(clauses, at_most_one_clauses):
if not clauses:
_logger.info('No clauses')
return None
@@ -55,7 +55,15 @@ def solve(clauses, at_most_one_clauses, decide):
for name, clause in at_most_one_clauses.items():
clauses[name] = problem.at_most_one(clause)
- if not problem.run_solver(lambda: decide(clauses)):
+ def decide():
+ for clause in clauses.values():
+ if clause.current is not None:
+ continue
+ v = clause.best_undecided()
+ if v is not None:
+ return v
+
+ if not problem.run_solver(decide):
return None
result = {}
diff --git a/tests/__init__.py b/tests/__init__.py
index 81c8b4e..dea79f6 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -313,7 +313,7 @@ class Test(unittest.TestCase):
if classes is None:
classes = [User, Context, Post]
self.node_volume = db.Volume('master', classes)
- self.node_routes = routes('guid', self.node_volume)
+ self.node_routes = routes('guid', volume=self.node_volume)
self.node_router = Router(self.node_routes)
self.node = coroutine.WSGIServer(('127.0.0.1', 8888), self.node_router)
coroutine.spawn(self.node.serve_forever)
diff --git a/tests/units/model/context.py b/tests/units/model/context.py
index 71357f2..ef505cc 100755
--- a/tests/units/model/context.py
+++ b/tests/units/model/context.py
@@ -77,12 +77,12 @@ class ContextTest(tests.Test):
'value': {
'license': ['Public Domain'],
'announce': next(volume['post'].find(query='title:1')[0]).guid,
- 'release': [[1], 0],
- 'requires': [],
- 'spec': {'*-*': {'bundle': str(hash(bundle1)), 'commands': {'activity': {'exec': 'true'}}, 'requires': {}}},
+ 'version': [[1], 0],
+ 'requires': {},
+ 'commands': {'activity': {'exec': 'true'}},
+ 'spec': {'*-*': {'bundle': str(hash(bundle1))}},
'stability': 'stable',
'unpack_size': len(activity_info1),
- 'version': '1',
},
},
}, conn.get(['context', context, 'releases']))
@@ -107,12 +107,12 @@ class ContextTest(tests.Test):
'value': {
'license': ['Public Domain'],
'announce': next(volume['post'].find(query='title:1')[0]).guid,
- 'release': [[1], 0],
- 'requires': [],
- 'spec': {'*-*': {'bundle': str(hash(bundle1)), 'commands': {'activity': {'exec': 'true'}}, 'requires': {}}},
+ 'version': [[1], 0],
+ 'requires': {},
+ 'commands': {'activity': {'exec': 'true'}},
+ 'spec': {'*-*': {'bundle': str(hash(bundle1))}},
'stability': 'stable',
'unpack_size': len(activity_info1),
- 'version': '1',
},
},
release2: {
@@ -121,12 +121,12 @@ class ContextTest(tests.Test):
'value': {
'license': ['Public Domain'],
'announce': next(volume['post'].find(query='title:2')[0]).guid,
- 'release': [[2], 0],
- 'requires': [],
- 'spec': {'*-*': {'bundle': str(hash(bundle2)), 'commands': {'activity': {'exec': 'true'}}, 'requires': {}}},
+ 'version': [[2], 0],
+ 'requires': {},
+ 'commands': {'activity': {'exec': 'true'}},
+ 'spec': {'*-*': {'bundle': str(hash(bundle2))}},
'stability': 'stable',
'unpack_size': len(activity_info2),
- 'version': '2',
},
},
}, conn.get(['context', context, 'releases']))
@@ -145,12 +145,12 @@ class ContextTest(tests.Test):
'value': {
'license': ['Public Domain'],
'announce': next(volume['post'].find(query='title:2')[0]).guid,
- 'release': [[2], 0],
- 'requires': [],
- 'spec': {'*-*': {'bundle': str(hash(bundle2)), 'commands': {'activity': {'exec': 'true'}}, 'requires': {}}},
+ 'version': [[2], 0],
+ 'requires': {},
+ 'commands': {'activity': {'exec': 'true'}},
+ 'spec': {'*-*': {'bundle': str(hash(bundle2))}},
'stability': 'stable',
'unpack_size': len(activity_info2),
- 'version': '2',
},
},
}, conn.get(['context', context, 'releases']))
diff --git a/tests/units/model/model.py b/tests/units/model/model.py
index f8b3866..f3e4442 100755
--- a/tests/units/model/model.py
+++ b/tests/units/model/model.py
@@ -74,16 +74,16 @@ class ModelTest(tests.Test):
'name': 'Activity-1',
}, files.get(blob.digest))
self.assertEqual('bundle_id', context)
- self.assertEqual('1', release['version'])
+ self.assertEqual([[1], 0], release['version'])
self.assertEqual('developer', release['stability'])
self.assertEqual(['Public Domain'], release['license'])
self.assertEqual('developer', release['stability'])
- self.assertEqual(sorted(['dep', 'sugar-0.88']), sorted(release['requires']))
+ self.assertEqual(
+ {'dep': {}, 'sugar': {'restrictions': [('0.88', None)]}},
+ release['requires'])
self.assertEqual({
'*-*': {
'bundle': blob.digest,
- 'commands': {'activity': {'exec': 'true'}},
- 'requires': {'dep': {}, 'sugar': {'restrictions': [('0.88', None)]}},
},
},
release['spec'])
@@ -124,7 +124,7 @@ class ModelTest(tests.Test):
'name': 'NonActivity-2',
}, files.get(blob.digest))
self.assertEqual('bundle_id', context)
- self.assertEqual('2', release['version'])
+ self.assertEqual([[2], 0], release['version'])
self.assertEqual(['GPL'], release['license'])
post = volume['post'][release['announce']]
@@ -164,24 +164,24 @@ class ModelTest(tests.Test):
self.assertRaises(http.BadRequest, load_bundle, blob_wo_license, 'bundle_id')
volume['context'].update('bundle_id', {'releases': {
- 'new': {'release': 2, 'license': ['New']},
+ 'new': {'value': {'release': 2, 'license': ['New']}},
}})
this.request = Request(method='POST', path=['context', 'bundle_id'], principal=tests.UID)
context, release = load_bundle(blob_wo_license, 'bundle_id')
self.assertEqual(['New'], release['license'])
volume['context'].update('bundle_id', {'releases': {
- 'new': {'release': 2, 'license': ['New']},
- 'old': {'release': 1, 'license': ['Old']},
+ 'new': {'value': {'release': 2, 'license': ['New']}},
+ 'old': {'value': {'release': 1, 'license': ['Old']}},
}})
this.request = Request(method='POST', path=['context', 'bundle_id'], principal=tests.UID)
context, release = load_bundle(blob_wo_license, 'bundle_id')
self.assertEqual(['New'], release['license'])
volume['context'].update('bundle_id', {'releases': {
- 'new': {'release': 2, 'license': ['New']},
- 'old': {'release': 1, 'license': ['Old']},
- 'newest': {'release': 3, 'license': ['Newest']},
+ 'new': {'value': {'release': 2, 'license': ['New']}},
+ 'old': {'value': {'release': 1, 'license': ['Old']}},
+ 'newest': {'value': {'release': 3, 'license': ['Newest']}},
}})
this.request = Request(method='POST', path=['context', 'bundle_id'], principal=tests.UID)
context, release = load_bundle(blob_wo_license, 'bundle_id')
@@ -204,24 +204,24 @@ class ModelTest(tests.Test):
self.assertRaises(http.BadRequest, load_bundle, blob, 'bundle_id')
volume['context'].update('bundle_id', {'releases': {
- 'new': {'release': 2, 'license': ['New']},
+ 'new': {'value': {'release': 2, 'license': ['New']}},
}})
this.request = Request(method='POST', path=['context', 'bundle_id'], principal=tests.UID, version='1')
context, release = load_bundle(blob, 'bundle_id')
self.assertEqual(['New'], release['license'])
volume['context'].update('bundle_id', {'releases': {
- 'new': {'release': 2, 'license': ['New']},
- 'old': {'release': 1, 'license': ['Old']},
+ 'new': {'value': {'release': 2, 'license': ['New']}},
+ 'old': {'value': {'release': 1, 'license': ['Old']}},
}})
this.request = Request(method='POST', path=['context', 'bundle_id'], principal=tests.UID, version='1')
context, release = load_bundle(blob, 'bundle_id')
self.assertEqual(['New'], release['license'])
volume['context'].update('bundle_id', {'releases': {
- 'new': {'release': 2, 'license': ['New']},
- 'old': {'release': 1, 'license': ['Old']},
- 'newest': {'release': 3, 'license': ['Newest']},
+ 'new': {'value': {'release': 2, 'license': ['New']}},
+ 'old': {'value': {'release': 1, 'license': ['Old']}},
+ 'newest': {'value': {'release': 3, 'license': ['Newest']}},
}})
this.request = Request(method='POST', path=['context', 'bundle_id'], principal=tests.UID, version='1')
context, release = load_bundle(blob, 'bundle_id')
@@ -490,13 +490,16 @@ class ModelTest(tests.Test):
this.request = Request(method='POST', path=['context', 'bundle_id'], principal=tests.UID)
context, release = load_bundle(blob, 'bundle_id')
- self.assertEqual(
- sorted([
- 'dep1', 'dep2', 'dep3', 'dep4-31', 'dep5-40',
- 'dep6-6',
- 'dep7-1', 'dep7-2', 'dep7-3',
- ]),
- sorted(release['requires']))
+ self.assertEqual({
+ 'dep5': {'restrictions': [('40', None)]},
+ 'dep4': {'restrictions': [('31', None)]},
+ 'dep7': {'restrictions': [('1', '4')]},
+ 'dep6': {'restrictions': [('6', '7')]},
+ 'dep1': {},
+ 'dep3': {'restrictions': [(None, '21')]},
+ 'dep2': {'restrictions': [(None, '10')]},
+ },
+ release['requires'])
def test_load_bundle_IgnoreNotSupportedContextTypes(self):
volume = self.start_master()
diff --git a/tests/units/node/model.py b/tests/units/node/model.py
index 68215c1..13c1ef5 100755
--- a/tests/units/node/model.py
+++ b/tests/units/node/model.py
@@ -11,11 +11,12 @@ from sugar_network.db import files
from sugar_network.client import Connection, keyfile, api_url
from sugar_network.model.user import User
from sugar_network.model.post import Post
+from sugar_network.model.context import Context
from sugar_network.node import model, obs
from sugar_network.node.routes import NodeRoutes
from sugar_network.toolkit.coroutine import this
from sugar_network.toolkit.router import Request, Router
-from sugar_network.toolkit import i18n, http, coroutine, enforce
+from sugar_network.toolkit import spec, i18n, http, coroutine, enforce
class ModelTest(tests.Test):
@@ -446,7 +447,7 @@ class ModelTest(tests.Test):
{'lsb_id': 'Debian', 'lsb_release': '6.0', 'name': 'Debian-6.0', 'arches': ['x86']},
{'lsb_id': 'Debian', 'lsb_release': '7.0', 'name': 'Debian-7.0', 'arches': ['x86_64']},
])
- self.override(obs, 'resolve', lambda repo, arch, names: ['fake'])
+ self.override(obs, 'resolve', lambda repo, arch, names: {'version': '1.0'})
volume = self.start_master([User, model.Context])
conn = http.Connection(api_url.value, http.SugarAuth(keyfile.value))
@@ -467,10 +468,10 @@ class ModelTest(tests.Test):
'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}},
'value': {'binary': ['pkg1.bin', 'pkg2.bin'], 'devel': ['pkg3.devel']},
},
- 'status': {
- 'Gentoo-2.1': 'success',
- 'Debian-6.0': 'success',
- 'Debian-7.0': 'success',
+ 'resolves': {
+ 'Gentoo-2.1': {'status': 'success', 'packages': ['pkg1.bin', 'pkg2.bin', 'pkg3.devel'], 'version': [[1, 0], 0]},
+ 'Debian-6.0': {'status': 'success', 'packages': ['pkg1.bin', 'pkg2.bin', 'pkg3.devel'], 'version': [[1, 0], 0]},
+ 'Debian-7.0': {'status': 'success', 'packages': ['pkg1.bin', 'pkg2.bin', 'pkg3.devel'], 'version': [[1, 0], 0]},
},
},
volume['context'][guid]['releases'])
@@ -491,8 +492,8 @@ class ModelTest(tests.Test):
'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}},
'value': {'binary': ['pkg1.bin', 'pkg2.bin'], 'devel': ['pkg3.devel']},
},
- 'status': {
- 'Gentoo-2.1': 'success',
+ 'resolves': {
+ 'Gentoo-2.1': {'status': 'success', 'packages': ['pkg1.bin', 'pkg2.bin', 'pkg3.devel'], 'version': [[1, 0], 0]},
},
},
volume['context'][guid]['releases'])
@@ -513,8 +514,8 @@ class ModelTest(tests.Test):
'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}},
'value': {'binary': ['pkg1.bin', 'pkg2.bin'], 'devel': ['pkg3.devel']},
},
- 'status': {
- 'Debian-6.0': 'success',
+ 'resolves': {
+ 'Debian-6.0': {'status': 'success', 'packages': ['pkg1.bin', 'pkg2.bin', 'pkg3.devel'], 'version': [[1, 0], 0]},
},
},
volume['context'][guid]['releases'])
@@ -544,8 +545,8 @@ class ModelTest(tests.Test):
'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}},
'value': {'binary': ['pkg1.bin', 'pkg2.bin'], 'devel': ['pkg3.devel']},
},
- 'status': {
- 'Gentoo-2.1': 'resolve failed',
+ 'resolves': {
+ 'Gentoo-2.1': {'status': 'resolve failed'},
},
},
volume['context'][guid]['releases'])
@@ -574,10 +575,10 @@ class ModelTest(tests.Test):
'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}},
'value': {'binary': ['1']},
},
- 'status': {
- 'Gentoo-2.1': '1',
- 'Debian-6.0': '1',
- 'Debian-7.0': '1',
+ 'resolves': {
+ 'Gentoo-2.1': {'status': '1'},
+ 'Debian-6.0': {'status': '1'},
+ 'Debian-7.0': {'status': '1'},
},
},
volume['context'][guid]['releases'])
@@ -595,10 +596,10 @@ class ModelTest(tests.Test):
'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}},
'value': {'binary': ['2']},
},
- 'status': {
- 'Gentoo-2.1': '1',
- 'Debian-6.0': '2',
- 'Debian-7.0': '2',
+ 'resolves': {
+ 'Gentoo-2.1': {'status': '1'},
+ 'Debian-6.0': {'status': '2'},
+ 'Debian-7.0': {'status': '2'},
},
},
volume['context'][guid]['releases'])
@@ -621,10 +622,10 @@ class ModelTest(tests.Test):
'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}},
'value': {'binary': ['3']},
},
- 'status': {
- 'Gentoo-2.1': '1',
- 'Debian-6.0': '3',
- 'Debian-7.0': '2',
+ 'resolves': {
+ 'Gentoo-2.1': {'status': '1'},
+ 'Debian-6.0': {'status': '3'},
+ 'Debian-7.0': {'status': '2'},
},
},
volume['context'][guid]['releases'])
@@ -647,14 +648,477 @@ class ModelTest(tests.Test):
'author': {tests.UID: {'name': tests.UID, 'order': 0, 'role': 3}},
'value': {'binary': ['3']},
},
- 'status': {
- 'Gentoo-2.1': '1',
- 'Debian-6.0': '3',
- 'Debian-7.0': '4',
+ 'resolves': {
+ 'Gentoo-2.1': {'status': '1'},
+ 'Debian-6.0': {'status': '3'},
+ 'Debian-7.0': {'status': '4'},
},
},
volume['context'][guid]['releases'])
+ def test_solve_SortByVersions(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+
+ context = volume['context'].create({
+ 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands1'}},
+ '2': {'value': {'stability': 'stable', 'version': 2, 'commands': 'commands2'}},
+ '3': {'value': {'stability': 'stable', 'version': 3, 'commands': 'commands3'}},
+ },
+ })
+ self.assertEqual(
+ {'commands': 'commands3', 'files': {context: '3'}, 'packages': {}},
+ model.solve(volume, context))
+
+ context = volume['context'].create({
+ 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '3': {'value': {'stability': 'stable', 'version': 3, 'commands': 'commands3'}},
+ '2': {'value': {'stability': 'stable', 'version': 2, 'commands': 'commands2'}},
+ '1': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands1'}},
+ },
+ })
+ self.assertEqual(
+ {'commands': 'commands3', 'files': {context: '3'}, 'packages': {}},
+ model.solve(volume, context))
+
+ def test_solve_SortByStability(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+
+ context = volume['context'].create({
+ 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'developer', 'version': 1, 'commands': 'commands1'}},
+ '2': {'value': {'stability': 'stable', 'version': 2, 'commands': 'commands2'}},
+ '3': {'value': {'stability': 'buggy', 'version': 3, 'commands': 'commands3'}},
+ },
+ })
+ self.assertEqual(
+ {'commands': 'commands2', 'files': {context: '2'}, 'packages': {}},
+ model.solve(volume, context))
+
+ def test_solve_CollectDeps(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {
+ 'stability': 'stable',
+ 'version': 1,
+ 'requires': spec.parse_requires('context2; context4'),
+ 'commands': 'commands',
+ }},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'context2', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '2': {'value': {
+ 'stability': 'stable',
+ 'version': 2,
+ 'requires': spec.parse_requires('context3'),
+ }},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'context3', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '3': {'value': {'stability': 'stable', 'version': 3}},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'context4', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '4': {'value': {'stability': 'stable', 'version': 4}},
+ },
+ })
+
+ self.assertEqual({
+ 'commands': 'commands',
+ 'files': {'context3': '3', 'context2': '2', 'context1': '1', 'context4': '4'},
+ 'packages': {},
+ },
+ model.solve(volume, 'context1'))
+
+ def test_solve_DepConditions(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+
+ volume['context'].create({
+ 'guid': 'dep', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': '1'}},
+ '2': {'value': {'stability': 'stable', 'version': '2'}},
+ '3': {'value': {'stability': 'stable', 'version': '3'}},
+ '4': {'value': {'stability': 'stable', 'version': '4'}},
+ '5': {'value': {'stability': 'stable', 'version': '5'}},
+ },
+ })
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep < 3'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '2', 'context1': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context1'))
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep <= 3'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '3', 'context1': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context1'))
+
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep > 2'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '5', 'context1': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context1'))
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep >= 2'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '5', 'context1': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context1'))
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep > 2; dep < 5'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '4', 'context1': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context1'))
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep > 2; dep <= 3'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '3', 'context1': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context1'))
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep = 1'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '1', 'context1': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context1'))
+
+ def test_solve_SwitchToAlternativeBranch(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+
+ volume['context'].create({
+ 'guid': 'context1', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '6': {'value': {'stability': 'stable', 'version': '1', 'requires': spec.parse_requires('context4=1'), 'commands': 'commands6'}},
+ '1': {'value': {'stability': 'stable', 'version': '2', 'requires': spec.parse_requires('context2'), 'commands': 'commands1'}},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'context2', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '2': {'value': {'stability': 'stable', 'version': '1', 'requires': spec.parse_requires('context3; context4=1')}},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'context3', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '3': {'value': {'stability': 'stable', 'version': '1', 'requires': spec.parse_requires('context4=2')}},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'context4', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '4': {'value': {'stability': 'stable', 'version': '2'}},
+ '5': {'value': {'stability': 'stable', 'version': '1'}},
+ },
+ })
+
+ self.assertEqual(
+ {'files': {'context1': '6', 'context4': '5'}, 'commands': 'commands6', 'packages': {}},
+ model.solve(volume, 'context1'))
+
+ def test_solve_CommonDeps(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+
+ volume['context'].create({
+ 'guid': 'dep', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': '1'}},
+ '2': {'value': {'stability': 'stable', 'version': '2'}},
+ '3': {'value': {'stability': 'stable', 'version': '3'}},
+ '4': {'value': {'stability': 'stable', 'version': '4'}},
+ '5': {'value': {'stability': 'stable', 'version': '5'}},
+ },
+ })
+
+ volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {},
+ 'dependencies': 'dep=2',
+ 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires(''),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '2', 'context': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context'))
+
+ volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {},
+ 'dependencies': 'dep<5',
+ 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep>1'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '4', 'context': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context'))
+
+ volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {},
+ 'dependencies': 'dep<4',
+ 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep<5'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '3', 'context': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context'))
+
+ def test_solve_ExtraDeps(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+
+ volume['context'].create({
+ 'guid': 'dep', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': '1'}},
+ '2': {'value': {'stability': 'stable', 'version': '2'}},
+ '3': {'value': {'stability': 'stable', 'version': '3'}},
+ '4': {'value': {'stability': 'stable', 'version': '4'}},
+ '5': {'value': {'stability': 'stable', 'version': '5'}},
+ },
+ })
+
+ volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires(''),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '2', 'context': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context', requires='dep=2'))
+
+ volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep>1'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '4', 'context': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context', requires='dep<5'))
+
+ volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep<5'),
+ }},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '3', 'context': '10'}, 'commands': 'commands', 'packages': {}},
+ model.solve(volume, 'context', requires='dep<4'))
+
+ def test_solve_Nothing(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+ this.request = Request()
+
+ volume['context'].create({
+ 'guid': 'dep', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': '1'}},
+ '2': {'value': {'stability': 'stable', 'version': '2'}},
+ '3': {'value': {'stability': 'stable', 'version': '3'}},
+ '4': {'value': {'stability': 'stable', 'version': '4'}},
+ '5': {'value': {'stability': 'stable', 'version': '5'}},
+ },
+ })
+
+ volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ },
+ })
+ self.assertEqual(None, model.solve(volume, 'context'))
+
+ volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '10': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep=0'),
+ }},
+ },
+ })
+ self.assertEqual(None, model.solve(volume, 'context'))
+
+ def test_solve_Packages(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+ this.request = Request()
+
+ context = volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('package'),
+ }},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'package', 'type': ['package'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ 'resolves': {
+ 'Ubuntu-10.04': {'version': 1, 'packages': ['pkg1', 'pkg2']},
+ },
+ },
+ })
+ self.assertEqual(
+ {'files': {'context': '1'}, 'commands': 'commands', 'packages': {'package': ['pkg1', 'pkg2']}},
+ model.solve(volume, context, lsb_id='Ubuntu', lsb_release='10.04'))
+
+ context = volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('dep; package'),
+ }},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'dep', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '2': {'value': {'stability': 'stable', 'version': '1'}},
+ },
+ })
+ self.assertEqual(
+ {'files': {'dep': '2', 'context': '1'}, 'commands': 'commands', 'packages': {'package': ['pkg1', 'pkg2']}},
+ model.solve(volume, context, lsb_id='Ubuntu', lsb_release='10.04'))
+
+ def test_solve_PackagesByLsbId(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+ this.request = Request()
+
+ context = volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('package1'),
+ }},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'package1', 'type': ['package'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ 'Ubuntu': {'value': {'binary': ['bin1', 'bin2'], 'devel': ['devel1', 'devel2']}},
+ },
+ })
+ self.assertEqual(
+ {'files': {'context': '1'}, 'commands': 'commands', 'packages': {'package1': ['bin1', 'bin2', 'devel1', 'devel2']}},
+ model.solve(volume, context, lsb_id='Ubuntu'))
+
+ context = volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('package2'),
+ }},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'package2', 'type': ['package'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ 'Ubuntu': {'value': {'binary': ['bin']}},
+ 'resolves': {
+ 'Ubuntu-10.04': {'version': 1, 'packages': ['pkg1', 'pkg2']},
+ },
+ },
+ })
+ self.assertEqual(
+ {'files': {'context': '1'}, 'commands': 'commands', 'packages': {'package2': ['bin']}},
+ model.solve(volume, context, lsb_id='Ubuntu', lsb_release='fake'))
+
+ def test_solve_PackagesByCommonAlias(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+ this.request = Request()
+
+ context = volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('package1'),
+ }},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'package1', 'type': ['package'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '*': {'value': {'binary': ['pkg1']}},
+ 'Ubuntu': {'value': {'binary': ['pkg2']}},
+ 'resolves': {
+ 'Ubuntu-10.04': {'version': 1, 'packages': ['pkg3']},
+ },
+ },
+ })
+ self.assertEqual(
+ {'files': {'context': '1'}, 'commands': 'commands', 'packages': {'package1': ['pkg1']}},
+ model.solve(volume, context))
+ self.assertEqual(
+ {'files': {'context': '1'}, 'commands': 'commands', 'packages': {'package1': ['pkg1']}},
+ model.solve(volume, context, lsb_id='Fake'))
+ self.assertEqual(
+ {'files': {'context': '1'}, 'commands': 'commands', 'packages': {'package1': ['pkg1']}},
+ model.solve(volume, context, lsb_id='Fake', lsb_release='fake'))
+
+ def test_solve_NoPackages(self):
+ volume = db.Volume('master', [Context])
+ this.volume = volume
+ this.request = Request()
+
+ context = volume['context'].create({
+ 'guid': 'context', 'type': ['activity'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ '1': {'value': {'stability': 'stable', 'version': 1, 'commands': 'commands',
+ 'requires': spec.parse_requires('package'),
+ }},
+ },
+ })
+ volume['context'].create({
+ 'guid': 'package', 'type': ['package'], 'title': {}, 'summary': {}, 'description': {}, 'releases': {
+ },
+ })
+ self.assertEqual(None, model.solve(volume, context))
+
if __name__ == '__main__':
tests.main()
diff --git a/tests/units/node/node.py b/tests/units/node/node.py
index 0058918..9b1f702 100755
--- a/tests/units/node/node.py
+++ b/tests/units/node/node.py
@@ -27,6 +27,7 @@ from sugar_network.model.user import User
from sugar_network.model.context import Context
from sugar_network.model.user import User
from sugar_network.toolkit.router import Router, Request, Response, fallbackroute, ACL, route
+from sugar_network.toolkit.coroutine import this
from sugar_network.toolkit import http
@@ -39,10 +40,10 @@ class NodeTest(tests.Test):
stats_user.stats_user_rras.value = ['RRA:AVERAGE:0.5:1:100']
def test_UserStats(self):
- volume = db.Volume('db', model.RESOURCES)
- cp = NodeRoutes('guid', volume=volume)
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
- call(cp, method='POST', document='user', principal=tests.UID, content={
+ this.call(method='POST', path=['user'], principal=tests.UID, content={
'name': 'user',
'pubkey': tests.PUBKEY,
})
@@ -55,9 +56,9 @@ class NodeTest(tests.Test):
'rras': ['RRA:AVERAGE:0.5:1:4320', 'RRA:AVERAGE:0.5:5:2016'],
'step': stats_user.stats_user_step.value,
},
- call(cp, method='GET', cmd='stats-info', document='user', guid=tests.UID, principal=tests.UID))
+ this.call(method='GET', cmd='stats-info', path=['user', tests.UID], principal=tests.UID))
- call(cp, method='POST', cmd='stats-upload', document='user', guid=tests.UID, principal=tests.UID, content={
+ this.call(method='POST', cmd='stats-upload', path=['user', tests.UID], principal=tests.UID, content={
'name': 'test',
'values': [(ts + 1, {'field': '1'})],
})
@@ -69,9 +70,9 @@ class NodeTest(tests.Test):
'rras': ['RRA:AVERAGE:0.5:1:4320', 'RRA:AVERAGE:0.5:5:2016'],
'step': stats_user.stats_user_step.value,
},
- call(cp, method='GET', cmd='stats-info', document='user', guid=tests.UID, principal=tests.UID))
+ this.call(method='GET', cmd='stats-info', path=['user', tests.UID], principal=tests.UID))
- call(cp, method='POST', cmd='stats-upload', document='user', guid=tests.UID, principal=tests.UID, content={
+ this.call(method='POST', cmd='stats-upload', path=['user', tests.UID], principal=tests.UID, content={
'name': 'test',
'values': [(ts + 2, {'field': '2'})],
})
@@ -83,9 +84,9 @@ class NodeTest(tests.Test):
'rras': ['RRA:AVERAGE:0.5:1:4320', 'RRA:AVERAGE:0.5:5:2016'],
'step': stats_user.stats_user_step.value,
},
- call(cp, method='GET', cmd='stats-info', document='user', guid=tests.UID, principal=tests.UID))
+ this.call(method='GET', cmd='stats-info', path=['user', tests.UID], principal=tests.UID))
- call(cp, method='POST', cmd='stats-upload', document='user', guid=tests.UID, principal=tests.UID, content={
+ this.call(method='POST', cmd='stats-upload', path=['user', tests.UID], principal=tests.UID, content={
'name': 'test2',
'values': [(ts + 3, {'field': '3'})],
})
@@ -98,20 +99,20 @@ class NodeTest(tests.Test):
'rras': ['RRA:AVERAGE:0.5:1:4320', 'RRA:AVERAGE:0.5:5:2016'],
'step': stats_user.stats_user_step.value,
},
- call(cp, method='GET', cmd='stats-info', document='user', guid=tests.UID, principal=tests.UID))
+ this.call(method='GET', cmd='stats-info', path=['user', tests.UID], principal=tests.UID))
def test_HandleDeletes(self):
- volume = db.Volume('db', model.RESOURCES)
- cp = NodeRoutes('guid', volume=volume)
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
- guid = call(cp, method='POST', document='context', principal=tests.UID, content={
+ guid = this.call(method='POST', path=['context'], principal=tests.UID, content={
'type': 'activity',
'title': 'title',
'summary': 'summary',
'description': 'description',
})
- guid_path = 'db/context/%s/%s' % (guid[:2], guid)
+ guid_path = 'master/context/%s/%s' % (guid[:2], guid)
assert exists(guid_path)
self.assertEqual({
@@ -119,19 +120,19 @@ class NodeTest(tests.Test):
'title': 'title',
'layer': [],
},
- call(cp, method='GET', document='context', guid=guid, reply=['guid', 'title', 'layer']))
+ this.call(method='GET', path=['context', guid], reply=['guid', 'title', 'layer']))
self.assertEqual([], volume['context'].get(guid)['layer'])
def subscribe():
- for event in cp.subscribe():
+ for event in conn.subscribe():
events.append(event)
events = []
coroutine.spawn(subscribe)
coroutine.dispatch()
- call(cp, method='DELETE', document='context', guid=guid, principal=tests.UID)
+ this.call(method='DELETE', path=['context', guid], principal=tests.UID)
coroutine.dispatch()
- self.assertRaises(http.NotFound, call, cp, method='GET', document='context', guid=guid, reply=['guid', 'title'])
+ self.assertRaises(http.NotFound, this.call, method='GET', path=['context', guid], reply=['guid', 'title'])
self.assertEqual(['deleted'], volume['context'].get(guid)['layer'])
def test_DeletedRestoredHandlers(self):
@@ -167,18 +168,17 @@ class NodeTest(tests.Test):
self.assertEqual([False, True, False], trigger)
def test_RegisterUser(self):
- cp = NodeRoutes('guid', volume=db.Volume('db', [User]))
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
- guid = call(cp, method='POST', document='user', principal=tests.UID2, content={
+ guid = this.call(method='POST', path=['user'], principal=tests.UID2, content={
'name': 'user',
'pubkey': tests.PUBKEY,
})
assert guid is None
- self.assertEqual('user', call(cp, method='GET', document='user', guid=tests.UID, prop='name'))
+ self.assertEqual('user', this.call(method='GET', path=['user', tests.UID, 'name']))
def test_UnauthorizedCommands(self):
- volume = db.Volume('db', model.RESOURCES)
- volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
class Routes(NodeRoutes):
@@ -193,12 +193,17 @@ class NodeTest(tests.Test):
class Document(db.Resource):
pass
- cp = Routes('guid', volume=db.Volume('db', [User, Document]))
- guid = call(cp, method='POST', document='document', principal=tests.UID, content={})
+ volume = self.start_master([Document, User], Routes)
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
+ volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
+ guid = this.call(method='POST', path=['document'], principal=tests.UID, content={})
- self.assertRaises(http.Unauthorized, call, cp, method='GET', cmd='probe1', document='document', guid=guid)
- call(cp, method='GET', cmd='probe1', document='document', guid=guid, principal=tests.UID)
- call(cp, method='GET', cmd='probe2', document='document', guid=guid)
+ this.request = Request()
+ self.assertRaises(http.Unauthorized, this.call, method='GET', cmd='probe1', path=['document', guid])
+ this.request = Request()
+ this.call(method='GET', cmd='probe1', path=['document', guid], principal=tests.UID)
+ this.request = Request()
+ this.call(method='GET', cmd='probe2', path=['document', guid])
def test_ForbiddenCommands(self):
@@ -215,30 +220,38 @@ class NodeTest(tests.Test):
class Document(db.Resource):
pass
- volume = db.Volume('db', [User, Document])
- cp = Routes('guid', volume=volume)
+ volume = self.start_master([Document, User], Routes)
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
+
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
+ volume['user'].create({'guid': tests.UID2, 'name': 'user2', 'pubkey': tests.PUBKEY2})
+
+ guid = this.call(method='POST', path=['document'], principal=tests.UID, content={})
- guid = call(cp, method='POST', document='document', principal=tests.UID, content={})
+ self.assertRaises(http.Forbidden, this.call, method='GET', cmd='probe1', path=['document', guid], principal=tests.UID2)
+ this.call(method='GET', cmd='probe1', path=['document', guid], principal=tests.UID)
- self.assertRaises(http.Forbidden, call, cp, method='GET', cmd='probe1', document='document', guid=guid)
- self.assertRaises(http.Forbidden, call, cp, method='GET', cmd='probe1', document='document', guid=guid, principal=tests.UID2)
- call(cp, method='GET', cmd='probe1', document='document', guid=guid, principal=tests.UID)
- call(cp, method='GET', cmd='probe2', document='document', guid=guid)
+ this.call(method='GET', cmd='probe2', path=['document', guid], principal=tests.UID2)
+ this.call(method='GET', cmd='probe2', path=['document', guid])
def test_ForbiddenCommandsForUserResource(self):
- cp = NodeRoutes('guid', volume=db.Volume('db', [User]))
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
- call(cp, method='POST', document='user', principal=tests.UID2, content={
+ this.call(method='POST', path=['user'], principal=tests.UID2, content={
'name': 'user1',
'pubkey': tests.PUBKEY,
})
- self.assertEqual('user1', call(cp, method='GET', document='user', guid=tests.UID, prop='name'))
+ self.assertEqual('user1', this.call(method='GET', path=['user', tests.UID, 'name']))
- self.assertRaises(http.Unauthorized, call, cp, method='PUT', document='user', guid=tests.UID, content={'name': 'user2'})
- self.assertRaises(http.Forbidden, call, cp, method='PUT', document='user', guid=tests.UID, principal=tests.UID2, content={'name': 'user2'})
- call(cp, method='PUT', document='user', guid=tests.UID, principal=tests.UID, content={'name': 'user2'})
- self.assertEqual('user2', call(cp, method='GET', document='user', guid=tests.UID, prop='name'))
+ this.request = Request()
+ self.assertRaises(http.Unauthorized, this.call, method='PUT', path=['user', tests.UID], content={'name': 'user2'})
+ this.request = Request()
+ self.assertRaises(http.Forbidden, this.call, method='PUT', path=['user', tests.UID], principal=tests.UID2, content={'name': 'user2'})
+ this.request = Request()
+ this.call(method='PUT', path=['user', tests.UID], principal=tests.UID, content={'name': 'user2'})
+ this.request = Request()
+ self.assertEqual('user2', this.call(method='GET', path=['user', tests.UID, 'name']))
def test_authorize_Config(self):
self.touch(('authorization.conf', [
@@ -252,14 +265,14 @@ class NodeTest(tests.Test):
def probe(self):
return 'ok'
- volume = db.Volume('db', [User])
- cp = Routes('guid', volume=volume)
+ volume = self.start_master([User], Routes)
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
volume['user'].create({'guid': tests.UID2, 'name': 'test', 'pubkey': tests.PUBKEY2})
- self.assertRaises(http.Forbidden, call, cp, method='PROBE')
- self.assertRaises(http.Forbidden, call, cp, method='PROBE', principal=tests.UID2)
- self.assertEqual('ok', call(cp, method='PROBE', principal=tests.UID))
+ self.assertRaises(http.Forbidden, this.call, method='PROBE')
+ self.assertRaises(http.Forbidden, this.call, method='PROBE', principal=tests.UID2)
+ self.assertEqual('ok', this.call(method='PROBE', principal=tests.UID))
def test_authorize_OnlyAuthros(self):
@@ -269,13 +282,13 @@ class NodeTest(tests.Test):
def prop(self, value):
return value
- volume = db.Volume('db', [User, Document])
- cp = NodeRoutes('guid', volume=volume)
+ volume = self.start_master([User, Document])
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
volume['user'].create({'guid': tests.UID2, 'name': 'user', 'pubkey': tests.PUBKEY2})
- guid = call(cp, method='POST', document='document', principal=tests.UID, content={'prop': '1'})
- self.assertRaises(http.Forbidden, call, cp, 'PUT', document='document', guid=guid, content={'prop': '2'}, principal=tests.UID2)
+ guid = this.call(method='POST', path=['document'], principal=tests.UID, content={'prop': '1'})
+ self.assertRaises(http.Forbidden, this.call, method='PUT', path=['document', guid], content={'prop': '2'}, principal=tests.UID2)
self.assertEqual('1', volume['document'].get(guid)['prop'])
def test_authorize_FullWriteForRoot(self):
@@ -290,17 +303,17 @@ class NodeTest(tests.Test):
def prop(self, value):
return value
- volume = db.Volume('db', [User, Document])
- cp = NodeRoutes('guid', volume=volume)
+ volume = self.start_master([User, Document])
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
volume['user'].create({'guid': tests.UID2, 'name': 'user', 'pubkey': tests.PUBKEY2})
- guid = call(cp, method='POST', document='document', principal=tests.UID, content={'prop': '1'})
+ guid = this.call(method='POST', path=['document'], principal=tests.UID, content={'prop': '1'})
- call(cp, 'PUT', document='document', guid=guid, content={'prop': '2'}, principal=tests.UID)
+ this.call(method='PUT', path=['document', guid], content={'prop': '2'}, principal=tests.UID)
self.assertEqual('2', volume['document'].get(guid)['prop'])
- call(cp, 'PUT', document='document', guid=guid, content={'prop': '3'}, principal=tests.UID2)
+ this.call(method='PUT', path=['document', guid], content={'prop': '3'}, principal=tests.UID2)
self.assertEqual('3', volume['document'].get(guid)['prop'])
def test_authorize_LiveConfigUpdates(self):
@@ -311,16 +324,16 @@ class NodeTest(tests.Test):
def probe(self):
pass
- volume = db.Volume('db', [User])
- cp = Routes('guid', volume=volume)
+ volume = self.start_master([User], Routes)
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
- self.assertRaises(http.Forbidden, call, cp, 'PROBE', principal=tests.UID)
+ self.assertRaises(http.Forbidden, this.call, method='PROBE', principal=tests.UID)
self.touch(('authorization.conf', [
'[%s]' % tests.UID,
'root = True',
]))
- call(cp, 'PROBE', principal=tests.UID)
+ this.call(method='PROBE', principal=tests.UID)
def test_authorize_Anonymous(self):
@@ -334,26 +347,26 @@ class NodeTest(tests.Test):
def probe2(self, request):
pass
- volume = db.Volume('db', [User])
- cp = Routes('guid', volume=volume)
+ volume = self.start_master([User], Routes)
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
- self.assertRaises(http.Unauthorized, call, cp, 'PROBE1')
- self.assertRaises(http.Forbidden, call, cp, 'PROBE2')
+ self.assertRaises(http.Unauthorized, this.call, method='PROBE1')
+ self.assertRaises(http.Forbidden, this.call, method='PROBE2')
self.touch(('authorization.conf', [
'[anonymous]',
'user = True',
'root = True',
]))
- call(cp, 'PROBE1')
- call(cp, 'PROBE2')
+ this.call(method='PROBE1')
+ this.call(method='PROBE2')
def test_SetUser(self):
- volume = db.Volume('db', model.RESOURCES)
- cp = NodeRoutes('guid', volume=volume)
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
- guid = call(cp, method='POST', document='context', principal=tests.UID, content={
+ guid = this.call(method='POST', path=['context'], principal=tests.UID, content={
'type': 'activity',
'title': 'title',
'summary': 'summary',
@@ -361,45 +374,45 @@ class NodeTest(tests.Test):
})
self.assertEqual(
[{'guid': tests.UID, 'name': 'user', 'role': 3}],
- call(cp, method='GET', document='context', guid=guid, prop='author'))
+ this.call(method='GET', path=['context', guid, 'author']))
def test_find_MaxLimit(self):
- volume = db.Volume('db', model.RESOURCES)
- cp = NodeRoutes('guid', volume=volume)
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
- call(cp, method='POST', document='context', principal=tests.UID, content={
+ this.call(method='POST', path=['context'], principal=tests.UID, content={
'type': 'activity',
'title': 'title1',
'summary': 'summary',
'description': 'description',
})
- call(cp, method='POST', document='context', principal=tests.UID, content={
+ this.call(method='POST', path=['context'], principal=tests.UID, content={
'type': 'activity',
'title': 'title2',
'summary': 'summary',
'description': 'description',
})
- call(cp, method='POST', document='context', principal=tests.UID, content={
+ this.call(method='POST', path=['context'], principal=tests.UID, content={
'type': 'activity',
'title': 'title3',
'summary': 'summary',
'description': 'description',
})
- cp._find_limit = 3
- self.assertEqual(3, len(call(cp, method='GET', document='context', limit=1024)['result']))
- cp._find_limit = 2
- self.assertEqual(2, len(call(cp, method='GET', document='context', limit=1024)['result']))
- cp._find_limit = 1
- self.assertEqual(1, len(call(cp, method='GET', document='context', limit=1024)['result']))
+ self.node_routes._find_limit = 3
+ self.assertEqual(3, len(this.call(method='GET', path=['context'], limit=1024)['result']))
+ self.node_routes._find_limit = 2
+ self.assertEqual(2, len(this.call(method='GET', path=['context'], limit=1024)['result']))
+ self.node_routes._find_limit = 1
+ self.assertEqual(1, len(this.call(method='GET', path=['context'], limit=1024)['result']))
def test_DeletedDocuments(self):
- volume = db.Volume('db', model.RESOURCES)
- cp = NodeRoutes('guid', volume=volume)
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
- guid = call(cp, method='POST', document='context', principal=tests.UID, content={
+ guid = this.call(method='POST', path=['context'], principal=tests.UID, content={
'type': 'activity',
'title': 'title1',
'summary': 'summary',
@@ -409,20 +422,20 @@ class NodeTest(tests.Test):
'logo': '',
})
- call(cp, method='GET', document='context', guid=guid)
- self.assertNotEqual([], call(cp, method='GET', document='context')['result'])
+ this.call(method='GET', path=['context', guid])
+ self.assertNotEqual([], this.call(method='GET', path=['context'])['result'])
volume['context'].update(guid, {'layer': ['deleted']})
- self.assertRaises(http.NotFound, call, cp, method='GET', document='context', guid=guid)
- self.assertEqual([], call(cp, method='GET', document='context')['result'])
+ self.assertRaises(http.NotFound, this.call, method='GET', path=['context', guid])
+ self.assertEqual([], this.call(method='GET', path=['context'])['result'])
def test_CreateGUID(self):
# TODO Temporal security hole, see TODO
- volume = db.Volume('db', model.RESOURCES)
- cp = NodeRoutes('guid', volume=volume)
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
- call(cp, method='POST', document='context', principal=tests.UID, content={
+ this.call(method='POST', path=['context'], principal=tests.UID, content={
'guid': 'foo',
'type': 'activity',
'title': 'title',
@@ -431,14 +444,14 @@ class NodeTest(tests.Test):
})
self.assertEqual(
{'guid': 'foo', 'title': 'title'},
- call(cp, method='GET', document='context', guid='foo', reply=['guid', 'title']))
+ this.call(method='GET', path=['context', 'foo'], reply=['guid', 'title']))
def test_CreateMalformedGUID(self):
- volume = db.Volume('db', model.RESOURCES)
- cp = MasterRoutes('guid', volume)
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
- self.assertRaises(http.BadRequest, call, cp, method='POST', document='context', principal=tests.UID, content={
+ self.assertRaises(http.BadRequest, this.call, method='POST', path=['context'], principal=tests.UID, content={
'guid': '!?',
'type': 'activity',
'title': 'title',
@@ -447,18 +460,18 @@ class NodeTest(tests.Test):
})
def test_FailOnExistedGUID(self):
- volume = db.Volume('db', model.RESOURCES)
- cp = MasterRoutes('guid', volume)
+ volume = self.start_master()
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user', 'pubkey': tests.PUBKEY})
- guid = call(cp, method='POST', document='context', principal=tests.UID, content={
+ guid = this.call(method='POST', path=['context'], principal=tests.UID, content={
'type': 'activity',
'title': 'title',
'summary': 'summary',
'description': 'description',
})
- self.assertRaises(http.BadRequest, call, cp, method='POST', document='context', principal=tests.UID, content={
+ self.assertRaises(http.BadRequest, this.call, method='POST', path=['context'], principal=tests.UID, content={
'guid': guid,
'type': 'activity',
'title': 'title',
@@ -516,94 +529,6 @@ class NodeTest(tests.Test):
sorted(json.loads(response.content)))
assert 'last-modified' not in response.headers
- def test_Clone(self):
- volume = self.start_master()
- client = http.Connection(api_url.value, http.SugarAuth(keyfile.value))
-
- blob1 = self.zips(('topdir/activity/activity.info', '\n'.join([
- '[Activity]',
- 'name = TestActivitry',
- 'bundle_id = bundle_id',
- 'exec = true',
- 'icon = icon',
- 'activity_version = 1',
- 'license = Public Domain',
- 'requires = dep1',
- 'stability = stable',
- ])))
- release1 = json.load(client.request('POST', ['context'], blob1, params={'cmd': 'submit', 'initial': True}).raw)
-
- blob2 = self.zips(('topdir/activity/activity.info', '\n'.join([
- '[Activity]',
- 'name = TestActivitry',
- 'bundle_id = bundle_id',
- 'exec = true',
- 'icon = icon',
- 'activity_version = 2',
- 'license = Public Domain',
- 'requires = dep2 < 3; dep3',
- 'stability = stable',
- ])))
- release2 = json.load(client.request('POST', ['context'], blob2, params={'cmd': 'submit'}).raw)
-
- blob3 = self.zips(('topdir/activity/activity.info', '\n'.join([
- '[Activity]',
- 'name = TestActivitry',
- 'bundle_id = bundle_id',
- 'exec = true',
- 'icon = icon',
- 'activity_version = 3',
- 'license = Public Domain',
- 'requires = dep2 >= 2',
- 'stability = stable',
- ])))
- release3 = json.load(client.request('POST', ['context'], blob3, params={'cmd': 'submit'}).raw)
-
- blob4 = self.zips(('topdir/activity/activity.info', '\n'.join([
- '[Activity]',
- 'name = TestActivitry',
- 'bundle_id = bundle_id',
- 'exec = true',
- 'icon = icon',
- 'activity_version = 4',
- 'license = Public Domain',
- 'stability = developer',
- ])))
- release4 = json.load(client.request('POST', ['context'], blob4, params={'cmd': 'submit'}).raw)
-
- assert blob3 == client.get(['context', 'bundle_id'], cmd='clone')
- assert blob4 == client.get(['context', 'bundle_id'], cmd='clone', stability='developer')
- assert blob1 == client.get(['context', 'bundle_id'], cmd='clone', version='1')
-
- assert blob1 == client.get(['context', 'bundle_id'], cmd='clone', requires='dep1')
- assert blob3 == client.get(['context', 'bundle_id'], cmd='clone', requires='dep2')
- assert blob2 == client.get(['context', 'bundle_id'], cmd='clone', requires='dep2=1')
- assert blob3 == client.get(['context', 'bundle_id'], cmd='clone', requires='dep2=2')
- assert blob2 == client.get(['context', 'bundle_id'], cmd='clone', requires='dep3')
-
- self.assertRaises(http.NotFound, client.get, ['context', 'bundle_id'], cmd='clone', requires='dep4')
- self.assertRaises(http.NotFound, client.get, ['context', 'bundle_id'], cmd='clone', stability='foo')
-
- response = Response()
- client.call(Request(method='GET', path=['context', 'bundle_id'], cmd='clone'), response)
- announce = next(volume['post'].find(query='3', limit=1)[0]).guid
- self.assertEqual({
- 'license': ['Public Domain'],
- 'unpack_size': 162,
- 'stability': 'stable',
- 'version': '3',
- 'release': [[3], 0],
- 'announce': announce,
- 'requires': ['dep2-2'],
- 'spec': {
- '*-*': {
- 'commands': {'activity': {'exec': u'true'}},
- 'requires': {'dep2': {'restrictions': [['2', None]]}},
- 'bundle': str(hash(blob3)),
- },
- },
- }, response.meta)
-
def test_release(self):
volume = self.start_master()
conn = Connection(auth=http.SugarAuth(keyfile.value))
@@ -633,12 +558,12 @@ class NodeTest(tests.Test):
'value': {
'license': ['Public Domain'],
'announce': announce,
- 'release': [[1], 0],
- 'requires': [],
- 'spec': {'*-*': {'bundle': str(hash(bundle)), 'commands': {'activity': {'exec': 'true'}}, 'requires': {}}},
+ 'version': [[1], 0],
+ 'requires': {},
+ 'spec': {'*-*': {'bundle': str(hash(bundle))}},
+ 'commands': {'activity': {'exec': 'true'}},
'stability': 'developer',
'unpack_size': len(activity_info) + len(changelog),
- 'version': '1',
},
},
}, conn.get(['context', 'bundle_id', 'releases']))
@@ -655,6 +580,155 @@ class NodeTest(tests.Test):
'en-us': 'LOG',
}, post['message'])
+ def test_Solve(self):
+ volume = self.start_master()
+ conn = http.Connection(api_url.value, http.SugarAuth(keyfile.value))
+
+ activity_file = json.load(conn.request('POST', ['context'],
+ self.zips(('topdir/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'name = activity',
+ 'bundle_id = activity',
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 1',
+ 'license = Public Domain',
+ 'requires = dep; package',
+ ]))),
+ params={'cmd': 'submit', 'initial': True}).raw)
+ dep_file = json.load(conn.request('POST', ['context'],
+ self.zips(('topdir/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'name = dep',
+ 'bundle_id = dep',
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 2',
+ 'license = Public Domain',
+ ]))),
+ params={'cmd': 'submit', 'initial': True}).raw)
+ this.call(method='POST', path=['context'], principal=tests.UID, content={
+ 'guid': 'package',
+ 'type': 'package',
+ 'title': 'title',
+ 'summary': 'summary',
+ 'description': 'description',
+ })
+ conn.put(['context', 'package', 'releases', '*'], {'binary': ['package.bin']})
+
+ self.assertEqual({
+ 'commands': {'activity': {'exec': 'true'}},
+ 'files': {'dep': dep_file, 'activity': activity_file},
+ 'packages': {'package': ['package.bin']},
+ },
+ conn.get(['context', 'activity'], cmd='solve'))
+
+ def test_SolveWithArguments(self):
+ volume = self.start_master()
+ conn = http.Connection(api_url.value, http.SugarAuth(keyfile.value))
+
+ activity_file = json.load(conn.request('POST', ['context'],
+ self.zips(('topdir/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'name = activity',
+ 'bundle_id = activity',
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 1',
+ 'license = Public Domain',
+ 'stability = developer',
+ ]))),
+ params={'cmd': 'submit', 'initial': True}).raw)
+ activity_fake_file = json.load(conn.request('POST', ['context'],
+ self.zips(('topdir/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'name = activity',
+ 'bundle_id = activity',
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 2',
+ 'license = Public Domain',
+ ]))),
+ params={'cmd': 'submit'}).raw)
+ dep_file = json.load(conn.request('POST', ['context'],
+ self.zips(('topdir/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'name = dep',
+ 'bundle_id = dep',
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 2',
+ 'license = Public Domain',
+ 'stability = developer',
+ ]))),
+ params={'cmd': 'submit', 'initial': True}).raw)
+ this.call(method='POST', path=['context'], principal=tests.UID, content={
+ 'guid': 'package',
+ 'type': 'package',
+ 'title': 'title',
+ 'summary': 'summary',
+ 'description': 'description',
+ })
+ volume['context'].update('package', {'releases': {
+ 'resolves': {
+ 'Ubuntu-10.04': {'version': 1, 'packages': ['package.bin']},
+ 'Ubuntu-12.04': {'version': 2, 'packages': ['package-fake.bin']},
+ }}})
+
+ self.assertEqual({
+ 'commands': {'activity': {'exec': 'true'}},
+ 'files': {'dep': dep_file, 'activity': activity_file},
+ 'packages': {'package': ['package.bin']},
+ },
+ conn.get(['context', 'activity'], cmd='solve',
+ stability='developer', lsb_id='Ubuntu', lsb_release='10.04', requires=['dep', 'package']))
+
+ def test_Clone(self):
+ volume = self.start_master()
+ conn = http.Connection(api_url.value, http.SugarAuth(keyfile.value))
+
+ activity_info = '\n'.join([
+ '[Activity]',
+ 'name = activity',
+ 'bundle_id = activity',
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 1',
+ 'license = Public Domain',
+ 'requires = dep; package',
+ ])
+ activity_blob = self.zips(('topdir/activity/activity.info', activity_info))
+ activity_file = json.load(conn.request('POST', ['context'], activity_blob, params={'cmd': 'submit', 'initial': True}).raw)
+ dep_file = json.load(conn.request('POST', ['context'],
+ self.zips(('topdir/activity/activity.info', '\n'.join([
+ '[Activity]',
+ 'name = dep',
+ 'bundle_id = dep',
+ 'exec = true',
+ 'icon = icon',
+ 'activity_version = 2',
+ 'license = Public Domain',
+ ]))),
+ params={'cmd': 'submit', 'initial': True}).raw)
+ this.call(method='POST', path=['context'], principal=tests.UID, content={
+ 'guid': 'package',
+ 'type': 'package',
+ 'title': 'title',
+ 'summary': 'summary',
+ 'description': 'description',
+ })
+ conn.put(['context', 'package', 'releases', '*'], {'binary': ['package.bin']})
+
+ response = Response()
+ reply = conn.call(Request(method='GET', path=['context', 'activity'], cmd='clone'), response)
+ assert activity_blob == reply.read()
+ self.assertEqual({
+ 'commands': {'activity': {'exec': 'true'}},
+ 'files': {'activity': activity_file, 'dep': dep_file},
+ 'packages': {'package': ['package.bin']},
+ },
+ response.meta)
+
def test_AggpropInsertAccess(self):
class Document(db.Resource):
@@ -667,28 +741,28 @@ class NodeTest(tests.Test):
def prop2(self, value):
return value
- volume = db.Volume('db', [Document, User])
- cp = NodeRoutes('node', volume=volume)
+ volume = self.start_master([Document, User])
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user1', 'pubkey': tests.PUBKEY})
volume['user'].create({'guid': tests.UID2, 'name': 'user2', 'pubkey': tests.PUBKEY2})
- guid = call(cp, method='POST', document='document', principal=tests.UID, content={})
+ guid = this.call(method='POST', path=['document'], principal=tests.UID, content={})
self.override(time, 'time', lambda: 0)
- agg1 = call(cp, method='POST', path=['document', guid, 'prop1'], principal=tests.UID)
- agg2 = call(cp, method='POST', path=['document', guid, 'prop1'], principal=tests.UID2)
+ agg1 = this.call(method='POST', path=['document', guid, 'prop1'], principal=tests.UID)
+ agg2 = this.call(method='POST', path=['document', guid, 'prop1'], principal=tests.UID2)
self.assertEqual({
agg1: {'seqno': 4, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}, 'value': None},
agg2: {'seqno': 5, 'author': {tests.UID2: {'name': 'user2', 'order': 0, 'role': 1}}, 'value': None},
},
- call(cp, method='GET', path=['document', guid, 'prop1']))
+ this.call(method='GET', path=['document', guid, 'prop1']))
- agg3 = call(cp, method='POST', path=['document', guid, 'prop2'], principal=tests.UID)
- self.assertRaises(http. Forbidden, call, cp, method='POST', path=['document', guid, 'prop2'], principal=tests.UID2)
+ agg3 = this.call(method='POST', path=['document', guid, 'prop2'], principal=tests.UID)
+ self.assertRaises(http. Forbidden, this.call, method='POST', path=['document', guid, 'prop2'], principal=tests.UID2)
self.assertEqual({
agg3: {'seqno': 6, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}, 'value': None},
},
- call(cp, method='GET', path=['document', guid, 'prop2']))
+ this.call(method='GET', path=['document', guid, 'prop2']))
def test_AggpropRemoveAccess(self):
@@ -702,86 +776,58 @@ class NodeTest(tests.Test):
def prop2(self, value):
return value
- volume = db.Volume('db', [Document, User])
- cp = NodeRoutes('node', volume=volume)
+ volume = self.start_master([Document, User])
+ conn = Connection(auth=http.SugarAuth(keyfile.value))
volume['user'].create({'guid': tests.UID, 'name': 'user1', 'pubkey': tests.PUBKEY})
volume['user'].create({'guid': tests.UID2, 'name': 'user2', 'pubkey': tests.PUBKEY2})
- guid = call(cp, method='POST', document='document', principal=tests.UID, content={})
+ guid = this.call(method='POST', path=['document'], principal=tests.UID, content={})
self.override(time, 'time', lambda: 0)
- agg1 = call(cp, method='POST', path=['document', guid, 'prop1'], principal=tests.UID, content=True)
- agg2 = call(cp, method='POST', path=['document', guid, 'prop1'], principal=tests.UID2, content=True)
+ agg1 = this.call(method='POST', path=['document', guid, 'prop1'], principal=tests.UID, content=True)
+ agg2 = this.call(method='POST', path=['document', guid, 'prop1'], principal=tests.UID2, content=True)
self.assertEqual({
agg1: {'seqno': 4, 'value': True, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}},
agg2: {'seqno': 5, 'value': True, 'author': {tests.UID2: {'name': 'user2', 'order': 0, 'role': 1}}},
},
- call(cp, method='GET', path=['document', guid, 'prop1']))
- self.assertRaises(http.Forbidden, call, cp, method='DELETE', path=['document', guid, 'prop1', agg1], principal=tests.UID2)
- self.assertRaises(http.Forbidden, call, cp, method='DELETE', path=['document', guid, 'prop1', agg2], principal=tests.UID)
+ this.call(method='GET', path=['document', guid, 'prop1']))
+ self.assertRaises(http.Forbidden, this.call, method='DELETE', path=['document', guid, 'prop1', agg1], principal=tests.UID2)
+ self.assertRaises(http.Forbidden, this.call, method='DELETE', path=['document', guid, 'prop1', agg2], principal=tests.UID)
self.assertEqual({
agg1: {'seqno': 4, 'value': True, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}},
agg2: {'seqno': 5, 'value': True, 'author': {tests.UID2: {'name': 'user2', 'order': 0, 'role': 1}}},
},
- call(cp, method='GET', path=['document', guid, 'prop1']))
+ this.call(method='GET', path=['document', guid, 'prop1']))
- call(cp, method='DELETE', path=['document', guid, 'prop1', agg1], principal=tests.UID)
+ this.call(method='DELETE', path=['document', guid, 'prop1', agg1], principal=tests.UID)
self.assertEqual({
agg1: {'seqno': 6, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}},
agg2: {'seqno': 5, 'value': True, 'author': {tests.UID2: {'name': 'user2', 'order': 0, 'role': 1}}},
},
- call(cp, method='GET', path=['document', guid, 'prop1']))
- call(cp, method='DELETE', path=['document', guid, 'prop1', agg2], principal=tests.UID2)
+ this.call(method='GET', path=['document', guid, 'prop1']))
+ this.call(method='DELETE', path=['document', guid, 'prop1', agg2], principal=tests.UID2)
self.assertEqual({
agg1: {'seqno': 6, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}},
agg2: {'seqno': 7, 'author': {tests.UID2: {'name': 'user2', 'order': 0, 'role': 1}}},
},
- call(cp, method='GET', path=['document', guid, 'prop1']))
+ this.call(method='GET', path=['document', guid, 'prop1']))
- agg3 = call(cp, method='POST', path=['document', guid, 'prop2'], principal=tests.UID, content=True)
+ agg3 = this.call(method='POST', path=['document', guid, 'prop2'], principal=tests.UID, content=True)
self.assertEqual({
agg3: {'seqno': 8, 'value': True, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}},
},
- call(cp, method='GET', path=['document', guid, 'prop2']))
+ this.call(method='GET', path=['document', guid, 'prop2']))
- self.assertRaises(http.Forbidden, call, cp, method='DELETE', path=['document', guid, 'prop2', agg3], principal=tests.UID2)
+ self.assertRaises(http.Forbidden, this.call, method='DELETE', path=['document', guid, 'prop2', agg3], principal=tests.UID2)
self.assertEqual({
agg3: {'seqno': 8, 'value': True, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}},
},
- call(cp, method='GET', path=['document', guid, 'prop2']))
- call(cp, method='DELETE', path=['document', guid, 'prop2', agg3], principal=tests.UID)
+ this.call(method='GET', path=['document', guid, 'prop2']))
+ this.call(method='DELETE', path=['document', guid, 'prop2', agg3], principal=tests.UID)
self.assertEqual({
agg3: {'seqno': 9, 'author': {tests.UID: {'name': 'user1', 'order': 0, 'role': 3}}},
},
- call(cp, method='GET', path=['document', guid, 'prop2']))
-
-
-def call(routes, method, document=None, guid=None, prop=None, principal=None, content=None, path=None, **kwargs):
- if not path:
- path = ['']
- if document:
- path.append(document)
- if guid:
- path.append(guid)
- if prop:
- path.append(prop)
- env = {'REQUEST_METHOD': method,
- 'PATH_INFO': '/'.join(path),
- 'HTTP_HOST': '127.0.0.1',
- }
- if principal:
- key = RSA.load_key(join(tests.root, 'data', principal))
- nonce = int(time.time()) + 100
- data = hashlib.sha1('%s:%s' % (principal, nonce)).digest()
- signature = key.sign(data).encode('hex')
- env['HTTP_AUTHORIZATION'] = 'Sugar username="%s",nonce="%s",signature="%s"' % (principal, nonce, signature)
- request = Request(env)
- request.update(kwargs)
- request.cmd = kwargs.get('cmd')
- request.content = content
- request.principal = principal
- router = Router(routes)
- return router.call(request, Response())
+ this.call(method='GET', path=['document', guid, 'prop2']))
if __name__ == '__main__':
diff --git a/tests/units/toolkit/sat.py b/tests/units/toolkit/sat.py
index 4202789..f4b01b0 100755
--- a/tests/units/toolkit/sat.py
+++ b/tests/units/toolkit/sat.py
@@ -11,13 +11,13 @@ class SAT(tests.Test):
def test_AtMostOne(self):
self.assertEqual(
{'c': 1},
- sat.solve([[1]], {'c': [1]}, decide))
+ sat.solve([[1]], {'c': [1]}))
self.assertEqual(
{'c': 1},
- sat.solve([[1, 2]], {'c': [1, 2]}, decide))
+ sat.solve([[1, 2]], {'c': [1, 2]}))
self.assertEqual(
{'c': 1},
- sat.solve([[1, 2, 3]], {'c': [1, 2, 3]}, decide))
+ sat.solve([[1, 2, 3]], {'c': [1, 2, 3]}))
def test_DeepSolve(self):
self.assertEqual({
@@ -38,9 +38,9 @@ class SAT(tests.Test):
'c3': [3],
'c4': [4, 5],
},
- decide))
+ ))
- def test_SwitchToAnotherBranch(self):
+ def test_SwitchToAlternativeBranch(self):
self.assertEqual({
'c1': 6,
'c4': 4,
@@ -57,7 +57,7 @@ class SAT(tests.Test):
'c3': [3],
'c4': [4, 5],
},
- decide))
+ ))
def __test_zi(self):
from zeroinstall.injector import sat
@@ -85,20 +85,19 @@ class SAT(tests.Test):
c4 = problem.at_most_one([v4, v5])
- assert problem.run_solver(lambda: decide({'c1': c1, 'c2': c2, 'c3': c3, 'c4': c4}))
+ def decide(clauses):
+ for i in [c1, c2, c3, c4]:
+ if i.current is None:
+ r = i.best_undecided()
+ if r is not None:
+ return r
+
+ assert problem.run_solver(decide)
self.assertEqual(v6, c1.current)
self.assertEqual(None, c2.current)
self.assertEqual(None, c3.current)
self.assertEqual(v4, c4.current)
-def decide(clauses):
- for i in clauses.values():
- if i.current is None:
- r = i.best_undecided()
- if r is not None:
- return r
-
-
if __name__ == '__main__':
tests.main()