diff options
Diffstat (limited to 'tests/units/client/online_routes.py')
-rwxr-xr-x | tests/units/client/online_routes.py | 1778 |
1 files changed, 900 insertions, 878 deletions
diff --git a/tests/units/client/online_routes.py b/tests/units/client/online_routes.py index 891b239..79b01cb 100755 --- a/tests/units/client/online_routes.py +++ b/tests/units/client/online_routes.py @@ -7,12 +7,12 @@ import time import shutil import zipfile from cStringIO import StringIO -from os.path import exists +from os.path import exists, lexists, basename from __init__ import tests, src_root from sugar_network import client, db, model -from sugar_network.client import IPCConnection, journal, clones, injector, routes +from sugar_network.client import IPCConnection, journal, routes, implementations from sugar_network.toolkit import coroutine, http from sugar_network.toolkit.spec import Spec from sugar_network.client.routes import ClientRoutes, Request, Response @@ -30,26 +30,9 @@ import requests class OnlineRoutes(tests.Test): - def test_inline(self): - cp = ClientRoutes(Volume('client', model.RESOURCES), client.api_url.value) - assert not cp.inline() - - trigger = self.wait_for_events(cp, event='inline', state='online') - coroutine.sleep(1) - self.start_master() - trigger.wait(1) - assert trigger.value is None - assert not cp.inline() - - request = Request(method='GET', cmd='whoami') - cp.whoami(request, Response()) - trigger.wait() - assert cp.inline() - - trigger = self.wait_for_events(cp, event='inline', state='offline') - self.node.stop() - trigger.wait() - assert not cp.inline() + def setUp(self, fork_num=0): + tests.Test.setUp(self, fork_num) + self.override(implementations, '_activity_id_new', lambda: 'activity_id') def test_whoami(self): self.start_online_client() @@ -59,109 +42,102 @@ class OnlineRoutes(tests.Test): {'guid': tests.UID, 'roles': []}, ipc.get(cmd='whoami')) - def test_clone_Activities(self): - self.home_volume = self.start_online_client() + def test_Events(self): + local_volume = self.start_online_client() ipc = IPCConnection() - coroutine.spawn(clones.monitor, self.home_volume['context'], ['Activities']) + events = [] - context = ipc.post(['context'], { + def read_events(): + for event in ipc.subscribe(event='!commit'): + events.append(event) + coroutine.spawn(read_events) + coroutine.dispatch() + + guid = ipc.post(['context'], { 'type': 'activity', 'title': 'title', 'summary': 'summary', 'description': 'description', }) - impl = ipc.post(['implementation'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', + ipc.put(['context', guid], { + 'title': 'title_2', }) - self.node_volume['implementation'].update(impl, {'data': { - 'spec': { - '*-*': { - 'commands': { - 'activity': { - 'exec': 'true', - }, - }, - 'extract': 'TestActivitry', - }, - }, - 'blob': StringIO(self.zips(['TestActivitry/activity/activity.info', [ - '[Activity]', - 'name = TestActivitry', - 'bundle_id = %s' % context, - 'exec = false', - 'icon = icon', - 'activity_version = 1', - 'license=Public Domain', - ]])), - }}) - - assert not exists('Activities/TestActivitry/activity/activity.info') - assert not exists('Activities/TestActivitry_1/activity/activity.info') - self.assertEqual( - {'clone': 0, 'type': ['activity']}, - ipc.get(['context', context], reply=['clone'])) - - ipc.put(['context', context], 2, cmd='clone') - coroutine.sleep(.5) - - assert exists('Activities/TestActivitry/activity/activity.info') - assert not exists('Activities/TestActivitry_1/activity/activity.info') - self.assertEqual( - {'clone': 2}, - ipc.get(['context', context], reply=['clone'])) - - ipc.put(['context', context], 2, cmd='clone') - coroutine.sleep(.5) - - assert exists('Activities/TestActivitry/activity/activity.info') - assert not exists('Activities/TestActivitry_1/activity/activity.info') - self.assertEqual( - {'clone': 2}, - ipc.get(['context', context], reply=['clone'])) - - ipc.put(['context', context], 1, cmd='clone', force=1) - coroutine.sleep(.5) + coroutine.sleep(.1) + ipc.delete(['context', guid]) + coroutine.sleep(.1) - assert exists('Activities/TestActivitry/activity/activity.info') - assert exists('Activities/TestActivitry_1/activity/activity.info') - self.assertEqual( - {'clone': 2}, - ipc.get(['context', context], reply=['clone'])) + self.assertEqual([ + {'guid': guid, 'resource': 'context', 'event': 'create'}, + {'guid': guid, 'resource': 'context', 'event': 'update'}, + {'guid': guid, 'event': 'delete', 'resource': 'context'}, + ], + events) + del events[:] - ipc.put(['context', context], 0, cmd='clone') - coroutine.sleep(.5) + guid = self.node_volume['context'].create({ + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + self.node_volume['context'].update(guid, { + 'title': 'title_2', + }) + coroutine.sleep(.1) + self.node_volume['context'].delete(guid) + coroutine.sleep(.1) - assert not exists('Activities/TestActivitry/activity/activity.info') - assert not exists('Activities/TestActivitry_1/activity/activity.info') - self.assertEqual( - {'clone': 0}, - ipc.get(['context', context], reply=['clone'])) + self.assertEqual([ + {'guid': guid, 'resource': 'context', 'event': 'create'}, + {'guid': guid, 'resource': 'context', 'event': 'update'}, + {'guid': guid, 'event': 'delete', 'resource': 'context'}, + ], + events) + del events[:] - ipc.put(['context', context], 1, cmd='clone') - coroutine.sleep(.5) + guid = local_volume['context'].create({ + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + local_volume['context'].update(guid, { + 'title': 'title_2', + }) + coroutine.sleep(.1) + local_volume['context'].delete(guid) + coroutine.sleep(.1) - assert exists('Activities/TestActivitry/activity/activity.info') - self.assertEqual( - {'clone': 2}, - ipc.get(['context', context], reply=['clone'])) + self.assertEqual([], events) - trigger = self.wait_for_events(ipc, event='inline', state='offline') self.node.stop() - trigger.wait() - assert ipc.get(cmd='status')['route'] == 'offline' + coroutine.sleep(.1) + del events[:] - self.assertEqual( - {'clone': 2}, - ipc.get(['context', context], reply=['clone'])) + guid = local_volume['context'].create({ + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', + }) + local_volume['context'].update(guid, { + 'title': 'title_2', + }) + coroutine.sleep(.1) + local_volume['context'].delete(guid) + coroutine.sleep(.1) - def test_clone_ActivitiesWithStabilityPreferences(self): - self.home_volume = self.start_online_client() + self.assertEqual([ + {'guid': guid, 'resource': 'context', 'event': 'create'}, + {'guid': guid, 'resource': 'context', 'event': 'update'}, + {'guid': guid, 'event': 'delete', 'resource': 'context'}, + ], + events) + del events[:] + + def test_Feeds(self): + self.start_online_client() ipc = IPCConnection() - coroutine.spawn(clones.monitor, self.home_volume['context'], ['Activities']) context = ipc.post(['context'], { 'type': 'activity', @@ -169,7 +145,6 @@ class OnlineRoutes(tests.Test): 'summary': 'summary', 'description': 'description', }) - impl1 = ipc.post(['implementation'], { 'context': context, 'license': 'GPLv3+', @@ -177,216 +152,306 @@ class OnlineRoutes(tests.Test): 'stability': 'stable', 'notes': '', }) - info1 = '\n'.join([ - '[Activity]', - 'name = TestActivitry', - 'bundle_id = %s' % context, - 'exec = false', - 'icon = icon', - 'activity_version = 1', - 'license=Public Domain', - ]) self.node_volume['implementation'].update(impl1, {'data': { - 'spec': { - '*-*': { - 'commands': { - 'activity': { - 'exec': 'true', - }, - }, - 'extract': 'TestActivitry', - }, - }, - 'blob': StringIO(self.zips(['TestActivitry/activity/activity.info', info1])), + 'spec': {'*-*': {}}, }}) - impl2 = ipc.post(['implementation'], { 'context': context, 'license': 'GPLv3+', 'version': '2', - 'stability': 'testing', + 'stability': 'stable', 'notes': '', }) - info2 = '\n'.join([ - '[Activity]', - 'name = TestActivitry', - 'bundle_id = %s' % context, - 'exec = false', - 'icon = icon', - 'activity_version = 1', - 'license=Public Domain', - ]) self.node_volume['implementation'].update(impl2, {'data': { - 'spec': { - '*-*': { - 'commands': { - 'activity': { - 'exec': 'true', - }, - }, - 'extract': 'TestActivitry2', + 'spec': {'*-*': { + 'requires': { + 'dep1': {}, + 'dep2': {'restrictions': [['1', '2']]}, + 'dep3': {'restrictions': [[None, '2']]}, + 'dep4': {'restrictions': [['3', None]]}, }, - }, - 'blob': StringIO(self.zips(['TestActivitry2/activity/activity.info', info2])), + }}, }}) - ipc.put(['context', context], 2, cmd='clone') - coroutine.sleep(.5) - not exists('Activities/TestActivitry2/activity/activity.info') - self.assertEqual(info1, file('Activities/TestActivitry/activity/activity.info').read()) - - self.touch(('config', [ - '[stabilities]', - '%s = testing stable' % context, - ])) - Option.load(['config']) - - shutil.rmtree('cache/solutions') - ipc.put(['context', context], 2, cmd='clone', force=1) - coroutine.sleep(.5) - self.assertEqual(info2, file('Activities/TestActivitry2/activity/activity.info').read()) + self.assertEqual({ + 'implementations': [ + { + 'version': '1', + 'arch': '*-*', + 'stability': 'stable', + 'guid': impl1, + 'license': ['GPLv3+'], + }, + { + 'version': '2', + 'arch': '*-*', + 'stability': 'stable', + 'guid': impl2, + 'requires': { + 'dep1': {}, + 'dep2': {'restrictions': [['1', '2']]}, + 'dep3': {'restrictions': [[None, '2']]}, + 'dep4': {'restrictions': [['3', None]]}, + }, + 'license': ['GPLv3+'], + }, + ], + }, + ipc.get(['context', context], cmd='feed')) - def test_clone_ActivityImpl(self): - self.home_volume = self.start_online_client() + def test_BLOBs(self): + self.start_online_client() ipc = IPCConnection() - coroutine.spawn(clones.monitor, self.home_volume['context'], ['Activities']) - context = ipc.post(['context'], { + guid = ipc.post(['context'], { 'type': 'activity', 'title': 'title', 'summary': 'summary', 'description': 'description', }) + ipc.request('PUT', ['context', guid, 'preview'], 'image') - impl1 = ipc.post(['implementation'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', + self.assertEqual( + 'image', + ipc.request('GET', ['context', guid, 'preview']).content) + self.assertEqual( + {'preview': 'http://127.0.0.1:8888/context/%s/preview' % guid}, + ipc.get(['context', guid], reply=['preview'])) + self.assertEqual( + [{'preview': 'http://127.0.0.1:8888/context/%s/preview' % guid}], + ipc.get(['context'], reply=['preview'])['result']) + + self.assertEqual( + file(src_root + '/sugar_network/static/httpdocs/images/missing.png').read(), + ipc.request('GET', ['context', guid, 'icon']).content) + self.assertEqual( + {'icon': 'http://127.0.0.1:8888/static/images/missing.png'}, + ipc.get(['context', guid], reply=['icon'])) + self.assertEqual( + [{'icon': 'http://127.0.0.1:8888/static/images/missing.png'}], + ipc.get(['context'], reply=['icon'])['result']) + + def test_favorite(self): + local = self.start_online_client() + ipc = IPCConnection() + events = [] + + def read_events(): + for event in ipc.subscribe(event='!commit'): + events.append(event) + coroutine.spawn(read_events) + coroutine.dispatch() + + context1 = ipc.post(['context'], { + 'type': 'activity', + 'title': 'title1', + 'summary': 'summary', + 'description': 'description', + 'layer': ['foo'], + }) + context2 = ipc.post(['context'], { + 'type': 'activity', + 'title': 'title2', + 'summary': 'summary', + 'description': 'description', + 'layer': ['foo'], }) - self.node_volume['implementation'].update(impl1, {'data': { - 'blob': StringIO(self.zips(('TestActivity/activity/activity.info', [ - '[Activity]', - 'name = TestActivity', - 'bundle_id = %s' % context, - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license=GPLv3+', - ]))), - 'spec': { - '*-*': { - 'extract': 'TestActivity', - 'commands': {'activity': {'exec': 'true'}}, - 'requires': { - 'dep1': {}, - }, - }, - }, - }}) - impl2 = ipc.post(['implementation'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '2', - 'stability': 'stable', - 'notes': '', + self.assertEqual( + sorted([]), + sorted(ipc.get(['context'], layer='favorite')['result'])) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'], layer='foo')['result'])) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'])['result'])) + self.assertEqual( + sorted([{'guid': context1, 'layer': ['foo']}, {'guid': context2, 'layer': ['foo']}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + self.assertEqual({'layer': ['foo']}, ipc.get(['context', context1], reply='layer')) + self.assertEqual(['foo'], ipc.get(['context', context1, 'layer'])) + self.assertEqual({'layer': ['foo']}, ipc.get(['context', context2], reply='layer')) + self.assertEqual(['foo'], ipc.get(['context', context2, 'layer'])) + self.assertEqual( + sorted([]), + sorted([i['layer'] for i in local['context'].find(reply='layer')[0]])) + + del events[:] + ipc.put(['context', context1], True, cmd='favorite') + coroutine.sleep(.1) + + self.assertEqual([ + {'guid': context1, 'resource': 'context', 'event': 'update'}, + ], + events) + self.assertEqual( + sorted([{'guid': context1}]), + sorted(ipc.get(['context'], layer='favorite')['result'])) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'], layer='foo')['result'])) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'])['result'])) + self.assertEqual( + sorted([{'guid': context1, 'layer': ['foo', 'favorite']}, {'guid': context2, 'layer': ['foo']}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + self.assertEqual({'layer': ['foo', 'favorite']}, ipc.get(['context', context1], reply='layer')) + self.assertEqual(['foo', 'favorite'], ipc.get(['context', context1, 'layer'])) + self.assertEqual({'layer': ['foo']}, ipc.get(['context', context2], reply='layer')) + self.assertEqual(['foo'], ipc.get(['context', context2, 'layer'])) + self.assertEqual( + sorted([['foo', 'favorite']]), + sorted([i['layer'] for i in local['context'].find(reply='layer')[0]])) + + del events[:] + ipc.put(['context', context2], True, cmd='favorite') + coroutine.sleep(.1) + + self.assertEqual([ + {'guid': context2, 'resource': 'context', 'event': 'update'}, + ], + events) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'], layer='favorite')['result'])) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'], layer='foo')['result'])) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'])['result'])) + self.assertEqual( + sorted([{'guid': context1, 'layer': ['foo', 'favorite']}, {'guid': context2, 'layer': ['foo', 'favorite']}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + self.assertEqual({'layer': ['foo', 'favorite']}, ipc.get(['context', context1], reply='layer')) + self.assertEqual(['foo', 'favorite'], ipc.get(['context', context1, 'layer'])) + self.assertEqual({'layer': ['foo', 'favorite']}, ipc.get(['context', context2], reply='layer')) + self.assertEqual(['foo', 'favorite'], ipc.get(['context', context2, 'layer'])) + self.assertEqual( + sorted([(context1, ['foo', 'favorite']), (context2, ['foo', 'favorite'])]), + sorted([(i.guid, i['layer']) for i in local['context'].find(reply='layer')[0]])) + + del events[:] + ipc.put(['context', context1], False, cmd='favorite') + coroutine.sleep(.1) + + self.assertEqual([ + {'guid': context1, 'resource': 'context', 'event': 'update'}, + ], + events) + self.assertEqual( + sorted([{'guid': context2}]), + sorted(ipc.get(['context'], layer='favorite')['result'])) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'], layer='foo')['result'])) + self.assertEqual( + sorted([{'guid': context1}, {'guid': context2}]), + sorted(ipc.get(['context'])['result'])) + self.assertEqual( + sorted([{'guid': context1, 'layer': ['foo']}, {'guid': context2, 'layer': ['foo', 'favorite']}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + self.assertEqual({'layer': ['foo']}, ipc.get(['context', context1], reply='layer')) + self.assertEqual(['foo'], ipc.get(['context', context1, 'layer'])) + self.assertEqual({'layer': ['foo', 'favorite']}, ipc.get(['context', context2], reply='layer')) + self.assertEqual(['foo', 'favorite'], ipc.get(['context', context2, 'layer'])) + self.assertEqual( + sorted([(context1, ['foo']), (context2, ['foo', 'favorite'])]), + sorted([(i.guid, i['layer']) for i in local['context'].find(reply='layer')[0]])) + + def test_clone_Fails(self): + self.start_online_client([User, Context, Implementation]) + conn = IPCConnection() + events = [] + + def read_events(): + for event in conn.subscribe(event='!commit'): + events.append(event) + coroutine.spawn(read_events) + coroutine.dispatch() + + context = conn.post(['context'], { + 'type': 'activity', + 'title': 'title', + 'summary': 'summary', + 'description': 'description', }) - self.node_volume['implementation'].update(impl2, {'data': { - 'blob': StringIO(self.zips(('TestActivity/activity/activity.info', [ - '[Activity]', - 'name = TestActivity', - 'bundle_id = %s' % context, - 'exec = true', - 'icon = icon', - 'activity_version = 2', - 'license=GPLv3+', - ]))), - 'spec': { - '*-*': { - 'extract': 'TestActivity', - 'commands': {'activity': {'exec': 'true'}}, - 'requires': { - 'dep2': {}, - }, - }, - }, - }}) - impl3 = ipc.post(['implementation'], { + self.assertRaises(http.NotFound, conn.put, ['context', context], True, cmd='clone') + coroutine.dispatch() + self.assertEqual({ + 'event': 'failure', + 'method': 'PUT', + 'cmd': 'clone', + 'resource': 'context', + 'guid': context, + 'prop': None, + 'exception': 'NotFound', + 'error': """\ +Can't find all required implementations: +- %s -> (problem) + No known implementations at all""" % context, + }, + events[-1]) + assert not exists('cache/solutions/%s/%s' % (context[:2], context)) + + impl = conn.post(['implementation'], { 'context': context, 'license': 'GPLv3+', - 'version': '3', - 'stability': 'developer', + 'version': '1', + 'stability': 'stable', 'notes': '', }) - self.node_volume['implementation'].update(impl3, {'data': { - 'blob': StringIO(self.zips(('TestActivity/activity/activity.info', [ - '[Activity]', - 'name = TestActivity', - 'bundle_id = %s' % context, - 'exec = true', - 'icon = icon', - 'activity_version = 3', - 'license=GPLv3+', - ]))), + self.node_volume['implementation'].update(impl, {'data': { + 'extract': 'topdir', 'spec': { '*-*': { - 'extract': 'TestActivity', - 'commands': {'activity': {'exec': 'true'}}, - 'requires': { - 'dep3': {}, + 'commands': { + 'activity': { + 'exec': 'echo', + }, }, }, }, }}) - self.assertRaises(RuntimeError, ipc.put, ['context', context], 2, cmd='clone', nodeps=1, requires='foo') - coroutine.sleep(.1) - self.assertEqual({'clone': 0}, ipc.get(['context', context], reply=['clone'])) - assert not exists('Activities/TestActivity/activity/activity.info') - - ipc.put(['context', context], 2, cmd='clone', nodeps=1) - # XXX seems to be an ugly low level bug, removing the following sleep means not reasing HTTP response for the next request - coroutine.sleep(.1) - self.assertEqual({'clone': 2}, ipc.get(['context', context], reply=['clone'])) - self.assertEqual('2', Spec('Activities/TestActivity/activity/activity.info')['version']) - - ipc.put(['context', context], 0, cmd='clone') - coroutine.sleep(.1) - self.assertEqual({'clone': 0}, ipc.get(['context', context], reply=['clone'])) - assert not exists('Activities/TestActivity/activity/activity.info') - - ipc.put(['context', context], 2, cmd='clone', nodeps=1, stability='developer') - coroutine.sleep(.1) - self.assertEqual({'clone': 2}, ipc.get(['context', context], reply=['clone'])) - self.assertEqual('3', Spec('Activities/TestActivity/activity/activity.info')['version']) - - ipc.put(['context', context], 0, cmd='clone') - coroutine.sleep(.1) - self.assertEqual({'clone': 0}, ipc.get(['context', context], reply=['clone'])) - assert not exists('Activities/TestActivity/activity/activity.info') - - ipc.put(['context', context], 2, cmd='clone', nodeps=1, requires='dep1') - coroutine.sleep(.1) - self.assertEqual({'clone': 2}, ipc.get(['context', context], reply=['clone'])) - self.assertEqual('1', Spec('Activities/TestActivity/activity/activity.info')['version']) + self.assertRaises(http.NotFound, conn.put, ['context', context], True, cmd='clone') + coroutine.dispatch() + self.assertEqual({ + 'event': 'failure', + 'method': 'PUT', + 'cmd': 'clone', + 'resource': 'context', + 'guid': context, + 'prop': None, + 'exception': 'NotFound', + 'error': 'BLOB does not exist', + 'solution': [{ + 'command': ['echo'], + 'context': context, + 'guid': impl, + 'license': ['GPLv3+'], + 'extract': 'topdir', + 'stability': 'stable', + 'version': '1', + 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), + }], + }, + events[-1]) + assert not exists('cache/solutions/%s/%s' % (context[:2], context)) def test_clone_Content(self): - self.start_online_client() - updates = [] - - def journal_update(self, guid, data=None, preview=None, **kwargs): - if data is not None: - kwargs['data'] = data.read() - updates.append((guid, kwargs)) - - self.override(journal.Routes, '__init__', lambda *args: None) - self.override(journal.Routes, 'journal_update', journal_update) - self.override(journal.Routes, 'journal_delete', lambda self, guid: updates.append((guid,))) - + local = self.start_online_client([User, Context, Implementation]) ipc = IPCConnection() + events = [] + + def read_events(): + for event in ipc.subscribe(event='!commit'): + events.append(event) + coroutine.spawn(read_events) + coroutine.dispatch() context = ipc.post(['context'], { 'type': 'content', @@ -401,260 +466,540 @@ class OnlineRoutes(tests.Test): 'stability': 'stable', 'notes': '', }) - ipc.request('PUT', ['implementation', impl, 'data'], 'version_1') + blob = 'content' + self.node_volume['implementation'].update(impl, {'data': {'blob': StringIO(blob), 'foo': 'bar'}}) + clone_path = 'client/context/%s/%s/.clone' % (context[:2], context) - self.assertEqual({'clone': 0, 'type': ['content']}, ipc.get(['context', context], reply=['clone'])) - - ipc.put(['context', context], 2, cmd='clone') - self.touch('datastore/%s/%s/metadata/uid' % (context[:2], context)) + ipc.put(['context', context], True, cmd='clone') + coroutine.dispatch() - self.assertEqual([ - (context, {'activity_id': impl, 'data': 'version_1', 'description': 'description', 'title': 'title', 'mime_type': 'application/octet-stream'}), - ], - updates) + self.assertEqual({ + 'event': 'update', + 'guid': context, + 'resource': 'context', + }, + events[-1]) self.assertEqual( - {'clone': 2, 'type': ['content']}, - ipc.get(['context', context], reply=['clone'])) - del updates[:] - - ipc.request('PUT', ['implementation', impl, 'data'], 'version_2', - headers={'Content-Type': 'foo/bar'}) - ipc.put(['context', context], 2, cmd='clone') - + sorted([{'guid': context}]), + sorted(ipc.get(['context'], layer='clone')['result'])) self.assertEqual( - [], - updates) + sorted([{'guid': context}]), + sorted(ipc.get(['context'])['result'])) self.assertEqual( - {'clone': 2, 'type': ['content']}, - ipc.get(['context', context], reply=['clone'])) - - ipc.put(['context', context], 1, cmd='clone', force=1) - - self.assertEqual([ - (context, {'activity_id': impl, 'data': 'version_2', 'description': 'description', 'title': 'title', 'mime_type': 'foo/bar'}), - ], - updates) + sorted([{'guid': context, 'layer': ['clone']}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + self.assertEqual({'layer': ['clone']}, ipc.get(['context', context], reply='layer')) + self.assertEqual(['clone'], ipc.get(['context', context, 'layer'])) self.assertEqual( - {'clone': 2, 'type': ['content']}, - ipc.get(['context', context], reply=['clone'])) - del updates[:] + [(context, ['clone'])], + [(i.guid, i['layer']) for i in local['context'].find(reply='layer')[0]]) + self.assertEqual({ + 'layer': ['clone'], + 'type': ['content'], + 'author': {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, + 'title': {'en-us': 'title'}, + }, + local['context'].get(context).properties(['layer', 'type', 'author', 'title'])) + self.assertEqual({ + 'context': context, + 'license': ['GPLv3+'], + 'version': '1', + 'stability': 'stable', + }, + local['implementation'].get(impl).properties(['context', 'license', 'version', 'stability'])) + blob_path = 'client/implementation/%s/%s/data.blob' % (impl[:2], impl) + self.assertEqual({ + 'seqno': 5, + 'blob_size': len(blob), + 'blob': tests.tmpdir + '/' + blob_path, + 'mtime': int(os.stat(blob_path[:-5]).st_mtime), + 'foo': 'bar', + }, + local['implementation'].get(impl).meta('data')) + self.assertEqual('content', file(blob_path).read()) + assert exists(clone_path + '/data.blob') + assert not exists('cache/solutions/%s/%s' % (context[:2], context)) - ipc.put(['context', context], 0, cmd='clone') - shutil.rmtree('datastore/%s/%s' % (context[:2], context)) + ipc.put(['context', context], False, cmd='clone') + coroutine.dispatch() - self.assertEqual([ - (context,), - ], - updates) + self.assertEqual({ + 'event': 'update', + 'guid': context, + 'resource': 'context', + }, + events[-1]) self.assertEqual( - {'clone': 0, 'type': ['content']}, - ipc.get(['context', context], reply=['clone'])) - del updates[:] - - def test_clone_Artifacts(self): - self.start_online_client([User, Context, Implementation, Artifact]) - updates = [] + sorted([{'guid': context, 'layer': []}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + self.assertEqual({'layer': []}, ipc.get(['context', context], reply='layer')) + self.assertEqual([], ipc.get(['context', context, 'layer'])) + self.assertEqual({ + 'layer': [], + 'type': ['content'], + 'author': {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, + 'title': {'en-us': 'title'}, + }, + local['context'].get(context).properties(['layer', 'type', 'author', 'title'])) + blob_path = 'client/implementation/%s/%s/data.blob' % (impl[:2], impl) + self.assertEqual({ + 'seqno': 5, + 'blob_size': len(blob), + 'blob': tests.tmpdir + '/' + blob_path, + 'mtime': int(os.stat(blob_path[:-5]).st_mtime), + 'foo': 'bar', + }, + local['implementation'].get(impl).meta('data')) + self.assertEqual('content', file(blob_path).read()) + assert not lexists(clone_path) + assert not exists('cache/solutions/%s/%s' % (context[:2], context)) - def journal_update(self, guid, data=None, preview=None, **kwargs): - if data is not None: - kwargs['data'] = data.read() - updates.append((guid, kwargs)) + ipc.put(['context', context], True, cmd='clone') + coroutine.dispatch() - self.override(journal.Routes, '__init__', lambda *args: None) - self.override(journal.Routes, 'journal_update', journal_update) - self.override(journal.Routes, 'journal_delete', lambda self, guid: updates.append((guid,))) + self.assertEqual({ + 'event': 'update', + 'guid': context, + 'resource': 'context', + }, + events[-1]) + self.assertEqual( + sorted([{'guid': context, 'layer': ['clone']}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + assert exists(clone_path + '/data.blob') + assert not exists('cache/solutions/%s/%s' % (context[:2], context)) + def test_clone_Activity(self): + local = self.start_online_client([User, Context, Implementation]) ipc = IPCConnection() + events = [] - artifact = ipc.post(['artifact'], { - 'context': 'context', - 'type': 'instance', - 'title': 'title', - 'description': 'description', - }) - ipc.request('PUT', ['artifact', artifact, 'data'], 'data') + def read_events(): + for event in ipc.subscribe(event='!commit'): + events.append(event) + coroutine.spawn(read_events) + coroutine.dispatch() - self.assertEqual({'clone': 0}, ipc.get(['artifact', artifact], reply=['clone'])) + activity_info = '\n'.join([ + '[Activity]', + 'name = TestActivity', + 'bundle_id = bundle_id', + 'exec = true', + 'icon = icon', + 'activity_version = 1', + 'license=Public Domain', + ]) + blob = self.zips(['TestActivity/activity/activity.info', activity_info]) + impl = ipc.upload(['implementation'], StringIO(blob), cmd='release', initial=True) + clone_path = 'client/context/bu/bundle_id/.clone' + blob_path = tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl) + solution = [{ + 'guid': impl, + 'context': 'bundle_id', + 'extract': 'TestActivity', + 'license': ['Public Domain'], + 'stability': 'stable', + 'version': '1', + 'command': ['true'], + 'path': blob_path, + }] - ipc.put(['artifact', artifact], 2, cmd='clone') - self.touch('datastore/%s/%s/metadata/uid' % (artifact[:2], artifact)) + ipc.put(['context', 'bundle_id'], True, cmd='clone') + coroutine.dispatch() - self.assertEqual([ - (artifact, {'data': 'data', 'description': 'description', 'title': 'title', 'activity': 'context'}), - ], - updates) + self.assertEqual({ + 'event': 'update', + 'guid': 'bundle_id', + 'resource': 'context', + }, + events[-1]) self.assertEqual( - {'clone': 2}, - ipc.get(['artifact', artifact], reply=['clone'])) - del updates[:] - - ipc.put(['artifact', artifact], 2, cmd='clone') - + sorted([{'guid': 'bundle_id'}]), + sorted(ipc.get(['context'], layer='clone')['result'])) self.assertEqual( - [], - updates) + sorted([{'guid': 'bundle_id'}]), + sorted(ipc.get(['context'])['result'])) self.assertEqual( - {'clone': 2}, - ipc.get(['artifact', artifact], reply=['clone'])) + sorted([{'guid': 'bundle_id', 'layer': ['clone']}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + self.assertEqual({'layer': ['clone']}, ipc.get(['context', 'bundle_id'], reply='layer')) + self.assertEqual(['clone'], ipc.get(['context', 'bundle_id', 'layer'])) + self.assertEqual({ + 'layer': ['clone'], + 'type': ['activity'], + 'author': {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, + 'title': {'en-us': 'TestActivity'}, + }, + local['context'].get('bundle_id').properties(['layer', 'type', 'author', 'title'])) + self.assertEqual({ + 'context': 'bundle_id', + 'license': ['Public Domain'], + 'version': '1', + 'stability': 'stable', + }, + local['implementation'].get(impl).properties(['context', 'license', 'version', 'stability'])) + self.assertEqual({ + 'seqno': 5, + 'unpack_size': len(activity_info), + 'blob_size': len(blob), + 'blob': blob_path, + 'mtime': int(os.stat(blob_path[:-5]).st_mtime), + 'extract': 'TestActivity', + 'mime_type': 'application/vnd.olpc-sugar', + 'spec': { + '*-*': { + 'requires': {}, + 'commands': {'activity': {'exec': 'true'}}, + }, + }, + }, + local['implementation'].get(impl).meta('data')) + self.assertEqual(activity_info, file(blob_path + '/activity/activity.info').read()) + assert exists(clone_path + '/data.blob/activity/activity.info') + self.assertEqual( + [client.api_url.value, ['stable'], solution], + json.load(file('cache/solutions/bu/bundle_id'))) - ipc.request('PUT', ['artifact', artifact, 'data'], 'data_2') - ipc.put(['artifact', artifact], 1, cmd='clone', force=1) + ipc.put(['context', 'bundle_id'], False, cmd='clone') + coroutine.dispatch() - self.assertEqual([ - (artifact, {'data': 'data_2', 'description': 'description', 'title': 'title', 'activity': 'context'}), - ], - updates) + self.assertEqual({ + 'event': 'update', + 'guid': 'bundle_id', + 'resource': 'context', + }, + events[-1]) + self.assertEqual( + sorted([{'guid': 'bundle_id', 'layer': []}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + self.assertEqual({'layer': []}, ipc.get(['context', 'bundle_id'], reply='layer')) + self.assertEqual([], ipc.get(['context', 'bundle_id', 'layer'])) + self.assertEqual({ + 'layer': [], + 'type': ['activity'], + 'author': {tests.UID: {'role': 3, 'name': 'test', 'order': 0}}, + 'title': {'en-us': 'TestActivity'}, + }, + local['context'].get('bundle_id').properties(['layer', 'type', 'author', 'title'])) + self.assertEqual({ + 'seqno': 5, + 'unpack_size': len(activity_info), + 'blob_size': len(blob), + 'blob': blob_path, + 'mtime': int(os.stat(blob_path[:-5]).st_mtime), + 'extract': 'TestActivity', + 'mime_type': 'application/vnd.olpc-sugar', + 'spec': { + '*-*': { + 'requires': {}, + 'commands': {'activity': {'exec': 'true'}}, + }, + }, + }, + local['implementation'].get(impl).meta('data')) + self.assertEqual(activity_info, file(blob_path + '/activity/activity.info').read()) + assert not exists(clone_path) self.assertEqual( - {'clone': 2}, - ipc.get(['artifact', artifact], reply=['clone'])) - del updates[:] + [client.api_url.value, ['stable'], solution], + json.load(file('cache/solutions/bu/bundle_id'))) - ipc.put(['artifact', artifact], 0, cmd='clone') - shutil.rmtree('datastore/%s/%s' % (artifact[:2], artifact)) + ipc.put(['context', 'bundle_id'], True, cmd='clone') + coroutine.dispatch() - self.assertEqual([ - (artifact,), - ], - updates) + self.assertEqual({ + 'event': 'update', + 'guid': 'bundle_id', + 'resource': 'context', + }, + events[-1]) self.assertEqual( - {'clone': 0}, - ipc.get(['artifact', artifact], reply=['clone'])) - del updates[:] + sorted([{'guid': 'bundle_id', 'layer': ['clone']}]), + sorted(ipc.get(['context'], reply='layer')['result'])) + assert exists(clone_path + '/data.blob/activity/activity.info') + self.assertEqual( + [client.api_url.value, ['stable'], solution], + json.load(file('cache/solutions/bu/bundle_id'))) - def test_favorite(self): - self.start_online_client() + def test_clone_ActivityWithStabilityPreferences(self): + local = self.start_online_client([User, Context, Implementation]) ipc = IPCConnection() - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) + activity_info1 = '\n'.join([ + '[Activity]', + 'name = TestActivity', + 'bundle_id = bundle_id', + 'exec = true', + 'icon = icon', + 'activity_version = 1', + 'license = Public Domain', + ]) + blob1 = self.zips(['TestActivity/activity/activity.info', activity_info1]) + impl1 = ipc.upload(['implementation'], StringIO(blob1), cmd='release', initial=True) - self.assertEqual( - {'favorite': 0, 'type': ['activity']}, - ipc.get(['context', context], reply=['favorite'])) + activity_info2 = '\n'.join([ + '[Activity]', + 'name = TestActivity', + 'bundle_id = bundle_id', + 'exec = true', + 'icon = icon', + 'activity_version = 2', + 'license = Public Domain', + 'stability = buggy', + ]) + blob2 = self.zips(['TestActivity/activity/activity.info', activity_info2]) + impl2 = ipc.upload(['implementation'], StringIO(blob2), cmd='release', initial=True) - ipc.put(['context', context], True, cmd='favorite') - coroutine.sleep(.5) - self.assertEqual( - {'favorite': True}, - ipc.get(['context', context], reply=['favorite'])) + ipc.put(['context', 'bundle_id'], True, cmd='clone') + coroutine.dispatch() + self.assertEqual({'layer': ['clone']}, ipc.get(['context', 'bundle_id'], reply='layer')) + self.assertEqual([impl1], [i.guid for i in local['implementation'].find()[0]]) + self.assertEqual(impl1, basename(os.readlink('client/context/bu/bundle_id/.clone'))) - ipc.put(['context', context], False, cmd='favorite') - self.assertEqual( - {'favorite': False}, - ipc.get(['context', context], reply=['favorite'])) + self.touch(('config', [ + '[stabilities]', + 'bundle_id = buggy stable', + ])) + Option.load(['config']) - ipc.put(['context', context], True, cmd='favorite') - coroutine.sleep(.5) - self.assertEqual( - {'favorite': True}, - ipc.get(['context', context], reply=['favorite'])) + ipc.put(['context', 'bundle_id'], False, cmd='clone') + ipc.put(['context', 'bundle_id'], True, cmd='clone') + coroutine.dispatch() + self.assertEqual({'layer': ['clone']}, ipc.get(['context', 'bundle_id'], reply='layer')) + self.assertEqual([impl1, impl2], [i.guid for i in local['implementation'].find()[0]]) + self.assertEqual(impl2, basename(os.readlink('client/context/bu/bundle_id/.clone'))) - trigger = self.wait_for_events(ipc, event='inline', state='offline') - self.node.stop() - trigger.wait() - assert ipc.get(cmd='status')['route'] == 'offline' + def test_clone_Head(self): + local = self.start_online_client([User, Context, Implementation]) + ipc = IPCConnection() - self.assertEqual( - {'favorite': True}, - ipc.get(['context', context], reply=['favorite'])) + activity_info = '\n'.join([ + '[Activity]', + 'name = TestActivity', + 'bundle_id = bundle_id', + 'exec = true', + 'icon = icon', + 'activity_version = 1', + 'license = Public Domain', + ]) + blob = self.zips(['TestActivity/activity/activity.info', activity_info]) + impl = ipc.upload(['implementation'], StringIO(blob), cmd='release', initial=True) + blob_path = 'master/implementation/%s/%s/data.blob' % (impl[:2], impl) - def test_subscribe(self): - self.start_online_client() + self.assertEqual({ + 'guid': impl, + 'license': ['Public Domain'], + 'stability': 'stable', + 'version': '1', + 'context': 'bundle_id', + 'data': { + 'blob_size': len(blob), + 'extract': 'TestActivity', + 'mime_type': 'application/vnd.olpc-sugar', + 'mtime': int(os.stat(blob_path[:-5]).st_mtime), + 'seqno': 3, + 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {}}}, + 'unpack_size': len(activity_info), + }, + }, + ipc.head(['context', 'bundle_id'], cmd='clone')) + + ipc.put(['context', 'bundle_id'], True, cmd='clone') + coroutine.dispatch() + blob_path = tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl) + + self.assertEqual({ + 'guid': impl, + 'license': ['Public Domain'], + 'stability': 'stable', + 'version': '1', + 'context': 'bundle_id', + 'data': { + 'blob': blob_path, + 'blob_size': len(blob), + 'extract': 'TestActivity', + 'mime_type': 'application/vnd.olpc-sugar', + 'mtime': int(os.stat(blob_path[:-5]).st_mtime), + 'seqno': 5, + 'spec': {'*-*': {'commands': {'activity': {'exec': 'true'}}, 'requires': {}}}, + 'unpack_size': len(activity_info), + }, + }, + ipc.head(['context', 'bundle_id'], cmd='clone')) + + def test_launch_Activity(self): + local = self.start_online_client([User, Context, Implementation]) ipc = IPCConnection() - events = [] + + blob = self.zips(['TestActivity/activity/activity.info', [ + '[Activity]', + 'name = TestActivity', + 'bundle_id = bundle_id', + 'exec = true', + 'icon = icon', + 'activity_version = 1', + 'license=Public Domain', + ]]) + impl = ipc.upload(['implementation'], StringIO(blob), cmd='release', initial=True) + coroutine.sleep(.1) def read_events(): for event in ipc.subscribe(event='!commit'): events.append(event) - job = coroutine.spawn(read_events) - coroutine.dispatch(.1) + events = [] + coroutine.spawn(read_events) + coroutine.dispatch() - guid = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - coroutine.dispatch(.1) - ipc.put(['context', guid], { - 'title': 'title_2', - }) - coroutine.dispatch(.1) - ipc.delete(['context', guid]) - coroutine.sleep(.5) - job.kill() + ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar') + coroutine.sleep(.1) + solution = [{ + 'guid': impl, + 'context': 'bundle_id', + 'extract': 'TestActivity', + 'license': ['Public Domain'], + 'stability': 'stable', + 'version': '1', + 'command': ['true'], + 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), + }] + log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id.log' self.assertEqual([ - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, + {'event': 'exec', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['true', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, + {'event': 'exit', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['true', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, ], events) - del events[:] + assert local['implementation'].exists(impl) + self.assertEqual( + [client.api_url.value, ['stable'], solution], + json.load(file('cache/solutions/bu/bundle_id'))) - job = coroutine.spawn(read_events) - coroutine.dispatch(.1) - guid = self.node_volume['context'].create({ - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - coroutine.dispatch(.1) - self.node_volume['context'].update(guid, { - 'title': 'title_2', - }) - coroutine.dispatch(.1) - self.node_volume['context'].delete(guid) - coroutine.dispatch(.1) - job.kill() + blob = self.zips(['TestActivity/activity/activity.info', [ + '[Activity]', + 'name = TestActivity', + 'bundle_id = bundle_id', + 'exec = true', + 'icon = icon', + 'activity_version = 2', + 'license=Public Domain', + ]]) + impl = ipc.upload(['implementation'], StringIO(blob), cmd='release') + coroutine.sleep(.1) + + shutil.rmtree('cache/solutions') + del events[:] + ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar') + coroutine.sleep(.1) + solution = [{ + 'guid': impl, + 'context': 'bundle_id', + 'extract': 'TestActivity', + 'license': ['Public Domain'], + 'stability': 'stable', + 'version': '2', + 'command': ['true'], + 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), + }] + log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id_1.log' self.assertEqual([ - {'guid': guid, 'resource': 'context', 'event': 'create'}, - {'guid': guid, 'resource': 'context', 'event': 'update'}, - {'guid': guid, 'event': 'delete', 'resource': 'context'}, + {'event': 'exec', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['true', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, + {'event': 'exit', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['true', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, ], events) + assert local['implementation'].exists(impl) + self.assertEqual( + [client.api_url.value, ['stable'], solution], + json.load(file('cache/solutions/bu/bundle_id'))) - def test_BLOBs(self): - self.start_online_client() - ipc = IPCConnection() + self.node.stop() + coroutine.sleep(.1) - guid = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - ipc.request('PUT', ['context', guid, 'preview'], 'image') + del events[:] + ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar') + coroutine.sleep(.1) + log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id_2.log' + self.assertEqual([ + {'event': 'exec', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['true', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, + {'event': 'exit', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['true', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, + ], + events) + assert local['implementation'].exists(impl) self.assertEqual( - 'image', - ipc.request('GET', ['context', guid, 'preview']).content) - self.assertEqual( - {'preview': 'http://127.0.0.1:8888/context/%s/preview' % guid}, - ipc.get(['context', guid], reply=['preview'])) - self.assertEqual( - [{'preview': 'http://127.0.0.1:8888/context/%s/preview' % guid}], - ipc.get(['context'], reply=['preview'])['result']) + [client.api_url.value, ['stable'], solution], + json.load(file('cache/solutions/bu/bundle_id'))) + shutil.rmtree('cache/solutions') + del events[:] + ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar') + coroutine.sleep(.1) + + log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id_3.log' + self.assertEqual([ + {'event': 'exec', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['true', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, + {'event': 'exit', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['true', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, + ], + events) + assert local['implementation'].exists(impl) self.assertEqual( - file(src_root + '/sugar_network/static/httpdocs/images/missing.png').read(), - ipc.request('GET', ['context', guid, 'icon']).content) - self.assertEqual( - {'icon': 'http://127.0.0.1:8888/static/images/missing.png'}, - ipc.get(['context', guid], reply=['icon'])) + [client.api_url.value, ['stable'], solution], + json.load(file('cache/solutions/bu/bundle_id'))) + + def test_launch_ActivityFailed(self): + local = self.start_online_client([User, Context, Implementation]) + ipc = IPCConnection() + + activity_info = '\n'.join([ + '[Activity]', + 'name = TestActivity', + 'bundle_id = bundle_id', + 'exec = false', + 'icon = icon', + 'activity_version = 1', + 'license=Public Domain', + ]) + blob = self.zips(['TestActivity/activity/activity.info', activity_info]) + impl = ipc.upload(['implementation'], StringIO(blob), cmd='release', initial=True) + coroutine.sleep(.1) + + def read_events(): + for event in ipc.subscribe(event='!commit'): + events.append(event) + events = [] + coroutine.spawn(read_events) + coroutine.dispatch() + + ipc.get(['context', 'bundle_id'], cmd='launch', foo='bar') + coroutine.sleep(.1) + + solution = [{ + 'guid': impl, + 'context': 'bundle_id', + 'extract': 'TestActivity', + 'license': ['Public Domain'], + 'stability': 'stable', + 'version': '1', + 'command': ['false'], + 'path': tests.tmpdir + '/client/implementation/%s/%s/data.blob' % (impl[:2], impl), + }] + log_path = tests.tmpdir + '/.sugar/default/logs/bundle_id.log' + self.assertEqual([ + {'event': 'exec', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['false', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, + {'event': 'failure', 'error': 'Process exited with 1 status', 'cmd': 'launch', 'guid': 'bundle_id', 'args': ['false', '-b', 'bundle_id', '-a', 'activity_id'], 'foo': 'bar', 'activity_id': 'activity_id', 'log_path': log_path, 'solution': solution}, + ], + events) + assert local['implementation'].exists(impl) self.assertEqual( - [{'icon': 'http://127.0.0.1:8888/static/images/missing.png'}], - ipc.get(['context'], reply=['icon'])['result']) + [client.api_url.value, ['stable'], solution], + json.load(file('cache/solutions/bu/bundle_id'))) - def test_Feeds(self): + def test_InvalidateSolutions(self): self.start_online_client() ipc = IPCConnection() + self.assertNotEqual(None, self.client_routes._node_mtime) + + mtime = self.client_routes._node_mtime + coroutine.sleep(1.1) context = ipc.post(['context'], { 'type': 'activity', @@ -662,6 +1007,10 @@ class OnlineRoutes(tests.Test): 'summary': 'summary', 'description': 'description', }) + assert self.client_routes._node_mtime == mtime + + coroutine.sleep(1.1) + impl1 = ipc.post(['implementation'], { 'context': context, 'license': 'GPLv3+', @@ -672,6 +1021,11 @@ class OnlineRoutes(tests.Test): self.node_volume['implementation'].update(impl1, {'data': { 'spec': {'*-*': {}}, }}) + assert self.client_routes._node_mtime > mtime + + mtime = self.client_routes._node_mtime + coroutine.sleep(1.1) + impl2 = ipc.post(['implementation'], { 'context': context, 'license': 'GPLv3+', @@ -689,37 +1043,34 @@ class OnlineRoutes(tests.Test): }, }}, }}) + assert self.client_routes._node_mtime > mtime - self.assertEqual({ - 'name': 'title', - 'implementations': [ - { - 'version': '1', - 'arch': '*-*', - 'stability': 'stable', - 'guid': impl1, - 'unpack_size': None, - 'blob_size': None, - }, - { - 'version': '2', - 'arch': '*-*', - 'stability': 'stable', - 'guid': impl2, - 'requires': { - 'dep1': {}, - 'dep2': {'restrictions': [['1', '2']]}, - 'dep3': {'restrictions': [[None, '2']]}, - 'dep4': {'restrictions': [['3', None]]}, - }, - 'unpack_size': None, - 'blob_size': None, - }, - ], - }, - ipc.get(['context', context], cmd='feed')) + def test_NoNeedlessRemoteRequests(self): + home_volume = self.start_online_client() + ipc = IPCConnection() + + guid = ipc.post(['context'], { + 'type': 'content', + 'title': 'remote', + 'summary': 'summary', + 'description': 'description', + }) + self.assertEqual( + {'title': 'remote'}, + ipc.get(['context', guid], reply=['title'])) + + home_volume['context'].create({ + 'guid': guid, + 'type': 'activity', + 'title': 'local', + 'summary': 'summary', + 'description': 'description', + }) + self.assertEqual( + {'title': 'local'}, + ipc.get(['context', guid], reply=['title'])) - def test_Feeds_RestrictLayers(self): + def test_RestrictLayers(self): self.start_online_client([User, Context, Implementation, Artifact]) ipc = IPCConnection() @@ -741,70 +1092,48 @@ class OnlineRoutes(tests.Test): self.node_volume['implementation'].update(impl, {'data': { 'spec': {'*-*': {}}, }}) - artifact = ipc.post(['artifact'], { - 'type': 'instance', - 'context': 'context', - 'title': 'title', - 'description': 'description', - 'layer': 'public', - }) - - self.assertEqual( - [{'layer': ['public']}], - ipc.get(['context'], reply='layer')['result']) - self.assertEqual( - [], - ipc.get(['context'], reply='layer', layer='foo')['result']) - self.assertEqual( - [{'layer': ['public']}], - ipc.get(['context'], reply='layer', layer='public')['result']) self.assertEqual( - [{'layer': ['public']}], - ipc.get(['implementation'], reply='layer')['result']) + [{'guid': context, 'layer': ['public']}], + ipc.get(['context'], reply=['guid', 'layer'])['result']) self.assertEqual( [], - ipc.get(['implementation'], reply='layer', layer='foo')['result']) + ipc.get(['context'], reply=['guid', 'layer'], layer='foo')['result']) self.assertEqual( - [{'layer': ['public']}], - ipc.get(['implementation'], reply='layer', layer='public')['result']) + [{'guid': context, 'layer': ['public']}], + ipc.get(['context'], reply=['guid', 'layer'], layer='public')['result']) self.assertEqual( - [{'layer': ['public']}], - ipc.get(['artifact'], reply='layer')['result']) + [{'guid': impl, 'layer': ['public']}], + ipc.get(['implementation'], reply=['guid', 'layer'])['result']) self.assertEqual( [], - ipc.get(['artifact'], reply='layer', layer='foo')['result']) + ipc.get(['implementation'], reply=['guid', 'layer'], layer='foo')['result']) self.assertEqual( - [{'layer': ['public']}], - ipc.get(['artifact'], reply='layer', layer='public')['result']) + [{'guid': impl, 'layer': ['public']}], + ipc.get(['implementation'], reply=['guid', 'layer'], layer='public')['result']) self.assertEqual({ - 'name': 'title', 'implementations': [{ 'stability': 'stable', 'guid': impl, 'arch': '*-*', 'version': '1', - 'unpack_size': None, - 'blob_size': None, + 'license': ['GPLv3+'], }], }, ipc.get(['context', context], cmd='feed')) self.assertEqual({ - 'name': 'title', 'implementations': [], }, ipc.get(['context', context], cmd='feed', layer='foo')) self.assertEqual({ - 'name': 'title', 'implementations': [{ 'stability': 'stable', 'guid': impl, 'arch': '*-*', 'version': '1', - 'unpack_size': None, - 'blob_size': None, + 'license': ['GPLv3+'], }], }, ipc.get(['context', context], cmd='feed', layer='public')) @@ -813,192 +1142,43 @@ class OnlineRoutes(tests.Test): self.assertEqual( [], - ipc.get(['context'], reply='layer')['result']) + ipc.get(['context'], reply=['guid', 'layer'])['result']) self.assertEqual( [], - ipc.get(['context'], reply='layer', layer='foo')['result']) + ipc.get(['context'], reply=['guid', 'layer'], layer='foo')['result']) self.assertEqual( - [{'layer': ['public']}], - ipc.get(['context'], reply='layer', layer='public')['result']) + [{'guid': context, 'layer': ['public']}], + ipc.get(['context'], reply=['guid', 'layer'], layer='public')['result']) self.assertEqual( [], - ipc.get(['implementation'], reply='layer')['result']) + ipc.get(['implementation'], reply=['guid', 'layer'])['result']) self.assertEqual( [], - ipc.get(['implementation'], reply='layer', layer='foo')['result']) + ipc.get(['implementation'], reply=['guid', 'layer'], layer='foo')['result']) self.assertEqual( - [{'layer': ['public']}], - ipc.get(['implementation'], reply='layer', layer='public')['result']) - - self.assertEqual( - [{'layer': ['public']}], - ipc.get(['artifact'], reply='layer')['result']) - self.assertEqual( - [], - ipc.get(['artifact'], reply='layer', layer='foo')['result']) - self.assertEqual( - [{'layer': ['public']}], - ipc.get(['artifact'], reply='layer', layer='public')['result']) + [{'guid': impl, 'layer': ['public']}], + ipc.get(['implementation'], reply=['guid', 'layer'], layer='public')['result']) self.assertEqual({ - 'name': 'title', 'implementations': [], }, ipc.get(['context', context], cmd='feed')) self.assertEqual({ - 'name': 'title', 'implementations': [], }, ipc.get(['context', context], cmd='feed', layer='foo')) self.assertEqual({ - 'name': 'title', 'implementations': [{ 'stability': 'stable', 'guid': impl, 'arch': '*-*', 'version': '1', - 'unpack_size': None, - 'blob_size': None, + 'license': ['GPLv3+'], }], }, ipc.get(['context', context], cmd='feed', layer='public')) - def test_Feeds_PreferLocalFeeds(self): - home_volume = self.start_online_client() - ipc = IPCConnection() - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - impl = ipc.post(['implementation'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '2', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['implementation'].update(impl, {'data': { - 'spec': {'*-*': {}}, - }}) - - self.assertEqual({ - 'name': 'title', - 'implementations': [ - { - 'version': '2', - 'arch': '*-*', - 'stability': 'stable', - 'guid': impl, - 'unpack_size': None, - 'blob_size': None, - }, - ], - }, - ipc.get(['context', context], cmd='feed')) - - self.touch(('Activities/activity-1/activity/activity.info', [ - '[Activity]', - 'name = TestActivity', - 'bundle_id = ' + context, - 'exec = true', - 'icon = icon', - 'activity_version = 1', - 'license = Public Domain', - ])) - monitor = coroutine.spawn(clones.monitor, home_volume['context'], ['Activities']) - coroutine.dispatch() - - self.assertEqual({ - 'name': 'TestActivity', - 'implementations': [ - { - 'version': '1', - 'arch': '*-*', - 'commands': { - 'activity': { - 'exec': 'true', - }, - }, - 'stability': 'stable', - 'guid': tests.tmpdir + '/Activities/activity-1', - 'requires': {}, - }, - ], - }, - ipc.get(['context', context], cmd='feed')) - - def test_InvalidateSolutions(self): - self.start_online_client() - ipc = IPCConnection() - self.assertNotEqual(None, injector._mtime) - - mtime = injector._mtime - coroutine.sleep(1.5) - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - assert injector._mtime == mtime - - impl1 = ipc.post(['implementation'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['implementation'].update(impl1, {'data': { - 'spec': {'*-*': {}}, - }}) - coroutine.sleep(.5) - assert injector._mtime > mtime - - mtime = injector._mtime - coroutine.sleep(1) - - impl2 = ipc.post(['implementation'], { - 'context': context, - 'license': 'GPLv3+', - 'version': '2', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['implementation'].update(impl2, {'data': { - 'spec': {'*-*': { - 'requires': { - 'dep1': {}, - 'dep2': {'restrictions': [['1', '2']]}, - 'dep3': {'restrictions': [[None, '2']]}, - 'dep4': {'restrictions': [['3', None]]}, - }, - }}, - }}) - assert injector._mtime > mtime - - def test_ContentDisposition(self): - self.start_online_client([User, Context, Implementation, Artifact]) - ipc = IPCConnection() - - artifact = ipc.post(['artifact'], { - 'type': 'instance', - 'context': 'context', - 'title': 'title', - 'description': 'description', - }) - ipc.request('PUT', ['artifact', artifact, 'data'], 'blob', headers={'Content-Type': 'image/png'}) - - response = ipc.request('GET', ['artifact', artifact, 'data']) - self.assertEqual( - 'attachment; filename="Title.png"', - response.headers.get('Content-Disposition')) - def test_Redirects(self): URL = 'http://sugarlabs.org' @@ -1016,201 +1196,22 @@ class OnlineRoutes(tests.Test): self.assertEqual(303, response.status_code) self.assertEqual(URL, response.headers['Location']) - def test_Proxy_Activities(self): - home_volume = self.start_online_client() - ipc = IPCConnection() - - context = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - - self.assertEqual( - [{'guid': context, 'favorite': False, 'clone': 0, 'type': ['activity']}], - ipc.get(['context'], reply=['favorite', 'clone'])['result']) - self.assertEqual( - {'favorite': False, 'clone': 0, 'type': ['activity']}, - ipc.get(['context', context], reply=['favorite', 'clone'])) - - home_volume['context'].create({ - 'guid': context, - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - 'favorite': True, - 'clone': 2, - }) - - self.assertEqual( - [{'guid': context, 'favorite': True, 'clone': 2, 'type': ['activity']}], - ipc.get(['context'], reply=['favorite', 'clone'])['result']) - self.assertEqual( - {'favorite': True, 'clone': 2}, - ipc.get(['context', context], reply=['favorite', 'clone'])) - - def test_Proxy_Content(self): - self.start_online_client([User, Context, Implementation, Artifact]) - ipc = IPCConnection() - - guid = ipc.post(['context'], { - 'type': 'content', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - - self.assertEqual( - [{'guid': guid, 'favorite': False, 'clone': 0, 'type': ['content']}], - ipc.get(['context'], reply=['favorite', 'clone'])['result']) - self.assertEqual( - {'favorite': False, 'clone': 0, 'type': ['content']}, - ipc.get(['context', guid], reply=['favorite', 'clone'])) - - self.touch(('datastore/%s/%s/metadata/keep' % (guid[:2], guid), '0')) - - self.assertEqual( - [{'guid': guid, 'favorite': False, 'clone': 2, 'type': ['content']}], - ipc.get(['context'], reply=['favorite', 'clone'])['result']) - self.assertEqual( - {'favorite': False, 'clone': 2, 'type': ['content']}, - ipc.get(['context', guid], reply=['favorite', 'clone'])) - - self.touch(('datastore/%s/%s/metadata/keep' % (guid[:2], guid), '1')) - - self.assertEqual( - [{'guid': guid, 'favorite': True, 'clone': 2, 'type': ['content']}], - ipc.get(['context'], reply=['favorite', 'clone'])['result']) - self.assertEqual( - {'favorite': True, 'clone': 2, 'type': ['content']}, - ipc.get(['context', guid], reply=['favorite', 'clone'])) - - def test_Proxy_Artifacts(self): + def test_ContentDisposition(self): self.start_online_client([User, Context, Implementation, Artifact]) ipc = IPCConnection() - guid = ipc.post(['artifact'], { + artifact = ipc.post(['artifact'], { 'type': 'instance', 'context': 'context', 'title': 'title', 'description': 'description', }) + ipc.request('PUT', ['artifact', artifact, 'data'], 'blob', headers={'Content-Type': 'image/png'}) + response = ipc.request('GET', ['artifact', artifact, 'data']) self.assertEqual( - [{'guid': guid, 'favorite': False, 'clone': 0}], - ipc.get(['artifact'], reply=['favorite', 'clone'])['result']) - self.assertEqual( - {'favorite': False, 'clone': 0}, - ipc.get(['artifact', guid], reply=['favorite', 'clone'])) - - self.touch(('datastore/%s/%s/metadata/keep' % (guid[:2], guid), '0')) - - self.assertEqual( - [{'guid': guid, 'favorite': False, 'clone': 2}], - ipc.get(['artifact'], reply=['favorite', 'clone'])['result']) - self.assertEqual( - {'favorite': False, 'clone': 2}, - ipc.get(['artifact', guid], reply=['favorite', 'clone'])) - - self.touch(('datastore/%s/%s/metadata/keep' % (guid[:2], guid), '1')) - - self.assertEqual( - [{'guid': guid, 'favorite': True, 'clone': 2}], - ipc.get(['artifact'], reply=['favorite', 'clone'])['result']) - self.assertEqual( - {'favorite': True, 'clone': 2}, - ipc.get(['artifact', guid], reply=['favorite', 'clone'])) - - def test_Proxy_NoNeedlessRemoteRequests(self): - home_volume = self.start_online_client() - ipc = IPCConnection() - - guid = ipc.post(['context'], { - 'type': 'content', - 'title': 'remote', - 'summary': 'summary', - 'description': 'description', - }) - self.assertEqual( - {'title': 'remote'}, - ipc.get(['context', guid], reply=['title'])) - - home_volume['context'].create({ - 'guid': guid, - 'type': 'activity', - 'title': 'local', - 'summary': 'summary', - 'description': 'description', - 'favorite': True, - }) - self.assertEqual( - {'title': 'local'}, - ipc.get(['context', guid], reply=['title'])) - - def test_HomeVolumeEvents(self): - self.home_volume = self.start_online_client() - ipc = IPCConnection() - coroutine.spawn(clones.monitor, self.home_volume['context'], ['Activities']) - - context1 = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - impl = ipc.post(['implementation'], { - 'context': context1, - 'license': 'GPLv3+', - 'version': '1', - 'stability': 'stable', - 'notes': '', - }) - self.node_volume['implementation'].update(impl, {'data': { - 'spec': { - '*-*': { - 'commands': { - 'activity': { - 'exec': 'true', - }, - }, - 'stability': 'stable', - 'size': 0, - 'extract': 'TestActivitry', - }, - }, - 'blob': StringIO(self.zips(['TestActivitry/activity/activity.info', [ - '[Activity]', - 'name = TestActivitry', - 'bundle_id = %s' % context1, - 'exec = false', - 'icon = icon', - 'activity_version = 1', - 'license=Public Domain', - ]])), - - }}) - - trigger = self.wait_for_events(ipc, event='update', resource='context', guid=context1) - ipc.put(['context', context1], 2, cmd='clone') - trigger.wait() - self.assertEqual( - {'clone': 2}, - ipc.get(['context', context1], reply=['clone'])) - - context2 = ipc.post(['context'], { - 'type': 'activity', - 'title': 'title', - 'summary': 'summary', - 'description': 'description', - }) - trigger = self.wait_for_events(ipc, event='create', resource='context', guid=context2) - ipc.put(['context', context2], True, cmd='favorite') - trigger.wait() - self.assertEqual( - {'favorite': True}, - ipc.get(['context', context2], reply=['favorite'])) + 'attachment; filename="Title.png"', + response.headers.get('Content-Disposition')) def test_FallbackToLocalSNOnRemoteTransportFails(self): @@ -1313,6 +1314,27 @@ class OnlineRoutes(tests.Test): self.fork_master([User]) self.wait_for_events(ipc, event='inline', state='online').wait() + def test_inline(self): + cp = ClientRoutes(Volume('client', model.RESOURCES), client.api_url.value) + assert not cp.inline() + + trigger = self.wait_for_events(cp, event='inline', state='online') + coroutine.sleep(1) + self.start_master() + trigger.wait(1) + assert trigger.value is None + assert not cp.inline() + + request = Request(method='GET', cmd='whoami') + cp.whoami(request, Response()) + trigger.wait() + assert cp.inline() + + trigger = self.wait_for_events(cp, event='inline', state='offline') + self.node.stop() + trigger.wait() + assert not cp.inline() + if __name__ == '__main__': tests.main() |