From d48da5dbad4f47d2aa6403ba13a8ca72fce5297f Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Fri, 21 Feb 2014 12:33:37 +0000 Subject: Implement context solver on node level --- 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 . +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() -- cgit v0.9.1