diff --git a/Dockerfile b/Dockerfile index 381c83f925..961d3ad8ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ # Usage: # cd to a folder where you want your game data to be (or where it already is). # -# docker run -it -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia +# docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia # # (If your OS does not support $PWD, replace it with the full path to your current # folder). @@ -15,6 +15,14 @@ # You will end up in a shell where the `evennia` command is available. From here you # can install and run the game normally. Use Ctrl-D to exit the evennia docker container. # +# You can also start evennia directly by passing arguments to the folder: +# +# docker run -it --rm -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia evennia start -l +# +# This will start Evennia running as the core process of the container. Note that you *must* use -l +# or one of the foreground modes (like evennia ipstart) since otherwise the container will immediately +# die since no foreground process keeps it up. +# # The evennia/evennia base image is found on DockerHub and can also be used # as a base for creating your own custom containerized Evennia game. For more # info, see https://github.com/evennia/evennia/wiki/Running%20Evennia%20in%20Docker . @@ -58,7 +66,7 @@ WORKDIR /usr/src/game ENV PS1 "evennia|docker \w $ " # startup a shell when we start the container -ENTRYPOINT bash -c "source /usr/src/evennia/bin/unix/evennia-docker-start.sh" +ENTRYPOINT ["/usr/src/evennia/bin/unix/evennia-docker-start.sh"] # expose the telnet, webserver and websocket client ports EXPOSE 4000 4001 4005 diff --git a/bin/unix/evennia-docker-start.sh b/bin/unix/evennia-docker-start.sh old mode 100644 new mode 100755 index d1333aaef6..5c87052da9 --- a/bin/unix/evennia-docker-start.sh +++ b/bin/unix/evennia-docker-start.sh @@ -1,10 +1,18 @@ -#! /bin/bash +#! /bin/sh # called by the Dockerfile to start the server in docker mode # remove leftover .pid files (such as from when dropping the container) rm /usr/src/game/server/*.pid >& /dev/null || true -# start evennia server; log to server.log but also output to stdout so it can -# be viewed with docker-compose logs -exec 3>&1; evennia start -l +PS1="evennia|docker \w $ " + +cmd="$@" +output="Docker starting with argument '$cmd' ..." +if test -z $cmd; then + cmd="bash" + output="No argument given, starting shell ..." +fi + +echo $output +exec 3>&1; $cmd diff --git a/evennia/accounts/tests.py b/evennia/accounts/tests.py index 2855dd0ca2..78ee87f37d 100644 --- a/evennia/accounts/tests.py +++ b/evennia/accounts/tests.py @@ -1,7 +1,8 @@ -from mock import Mock +from mock import Mock, MagicMock from random import randint from unittest import TestCase +from django.test import override_settings from evennia.accounts.accounts import AccountSessionHandler from evennia.accounts.accounts import DefaultAccount from evennia.server.session import Session @@ -14,9 +15,15 @@ class TestAccountSessionHandler(TestCase): "Check AccountSessionHandler class" def setUp(self): - self.account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.account = create.create_account( + "TestAccount%s" % randint(0, 999999), email="test@test.com", + password="testpassword", typeclass=DefaultAccount) self.handler = AccountSessionHandler(self.account) + def tearDown(self): + if hasattr(self, 'account'): + self.account.delete() + def test_get(self): "Check get method" self.assertEqual(self.handler.get(), []) @@ -24,24 +31,24 @@ class TestAccountSessionHandler(TestCase): import evennia.server.sessionhandler - s1 = Session() + s1 = MagicMock() s1.logged_in = True s1.uid = self.account.uid evennia.server.sessionhandler.SESSIONS[s1.uid] = s1 - s2 = Session() + s2 = MagicMock() s2.logged_in = True s2.uid = self.account.uid + 1 evennia.server.sessionhandler.SESSIONS[s2.uid] = s2 - s3 = Session() + s3 = MagicMock() s3.logged_in = False s3.uid = self.account.uid + 2 evennia.server.sessionhandler.SESSIONS[s3.uid] = s3 - self.assertEqual(self.handler.get(), [s1]) - self.assertEqual(self.handler.get(self.account.uid), [s1]) - self.assertEqual(self.handler.get(self.account.uid + 1), []) + self.assertEqual([s.uid for s in self.handler.get()], [s1.uid]) + self.assertEqual([s.uid for s in [self.handler.get(self.account.uid)]], [s1.uid]) + self.assertEqual([s.uid for s in self.handler.get(self.account.uid + 1)], []) def test_all(self): "Check all method" @@ -56,9 +63,14 @@ class TestDefaultAccount(TestCase): "Check DefaultAccount class" def setUp(self): - self.s1 = Session() + self.s1 = MagicMock() self.s1.puppet = None self.s1.sessid = 0 + self.s1.data_outj + + def tearDown(self): + if hasattr(self, "account"): + self.account.delete() def test_password_validation(self): "Check password validators deny bad passwords" @@ -71,7 +83,6 @@ class TestDefaultAccount(TestCase): "Check validators allow sufficiently complex passwords" for better in ('Mxyzptlk', "j0hn, i'M 0n1y d4nc1nG"): self.assertTrue(self.account.validate_password(better, account=self.account)[0]) - self.account.delete() def test_password_change(self): "Check password setting and validation is working as expected" @@ -109,7 +120,9 @@ class TestDefaultAccount(TestCase): import evennia.server.sessionhandler - account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + account = create.create_account( + "TestAccount%s" % randint(0, 999999), email="test@test.com", + password="testpassword", typeclass=DefaultAccount) self.s1.uid = account.uid evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 @@ -131,10 +144,7 @@ class TestDefaultAccount(TestCase): self.s1.uid = account.uid evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 - self.s1.puppet = None - self.s1.logged_in = True - self.s1.data_out = Mock(return_value=None) - + self.s1.data_out = MagicMock() obj = Mock() obj.access = Mock(return_value=False) @@ -143,6 +153,7 @@ class TestDefaultAccount(TestCase): self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet")) self.assertIsNone(obj.at_post_puppet.call_args) + @override_settings(MULTISESSION_MODE=0) def test_puppet_object_joining_other_session(self): "Check puppet_object method called, joining other session" @@ -154,15 +165,16 @@ class TestDefaultAccount(TestCase): self.s1.puppet = None self.s1.logged_in = True - self.s1.data_out = Mock(return_value=None) + self.s1.data_out = MagicMock() obj = Mock() obj.access = Mock(return_value=True) obj.account = account + obj.sessions.all = MagicMock(return_value=[self.s1]) account.puppet_object(self.s1, obj) # works because django.conf.settings.MULTISESSION_MODE is not in (1, 3) - self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.")) + self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.|n")) self.assertTrue(obj.at_post_puppet.call_args[1] == {}) def test_puppet_object_already_puppeted(self): @@ -171,6 +183,7 @@ class TestDefaultAccount(TestCase): import evennia.server.sessionhandler account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.account = account self.s1.uid = account.uid evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index ce08944139..ac6ad854b1 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -259,11 +259,11 @@ def prototype_from_object(obj): if aliases: prot['aliases'] = aliases tags = [(tag.db_key, tag.db_category, tag.db_data) - for tag in obj.tags.get(return_tagobj=True, return_list=True) if tag] + for tag in obj.tags.all(return_objs=True)] if tags: prot['tags'] = tags attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all())) - for attr in obj.attributes.get(return_obj=True, return_list=True) if attr] + for attr in obj.attributes.all()] if attrs: prot['attrs'] = attrs @@ -660,6 +660,7 @@ def spawn(*prototypes, **kwargs): protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()} if not kwargs.get("only_validate"): + # homogenization to be more lenient about prototype format when entering the prototype manually prototypes = [protlib.homogenize_prototype(prot) for prot in prototypes] # overload module's protparents with specifically given protparents @@ -714,7 +715,7 @@ def spawn(*prototypes, **kwargs): val = prot.pop("tags", []) tags = [] - for (tag, category, data) in tags: + for (tag, category, data) in val: tags.append((init_spawn_value(val, str), category, data)) prototype_key = prototype.get('prototype_key', None) diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 3cf2e38c7b..411bd45c27 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -134,6 +134,7 @@ class TestUtils(EvenniaTest): 'prototype_key': Something, 'prototype_locks': 'spawn:all();edit:all()', 'prototype_tags': [], + 'tags': [(u'footag', u'foocategory', None)], 'typeclass': 'evennia.objects.objects.DefaultObject'}) self.assertEqual(old_prot, @@ -182,6 +183,7 @@ class TestUtils(EvenniaTest): 'typeclass': ('evennia.objects.objects.DefaultObject', 'evennia.objects.objects.DefaultObject', 'KEEP'), 'aliases': {'foo': ('foo', None, 'REMOVE')}, + 'tags': {u'footag': ((u'footag', u'foocategory', None), None, 'REMOVE')}, 'prototype_desc': ('Built from Obj', 'New version of prototype', 'UPDATE'), 'permissions': {"Builder": (None, 'Builder', 'ADD')} @@ -200,6 +202,7 @@ class TestUtils(EvenniaTest): 'prototype_key': 'UPDATE', 'prototype_locks': 'KEEP', 'prototype_tags': 'KEEP', + 'tags': 'REMOVE', 'typeclass': 'KEEP'} ) diff --git a/evennia/typeclasses/attributes.py b/evennia/typeclasses/attributes.py index 863628172a..1dc1902494 100644 --- a/evennia/typeclasses/attributes.py +++ b/evennia/typeclasses/attributes.py @@ -668,7 +668,7 @@ class AttributeHandler(object): def all(self, accessing_obj=None, default_access=True): """ - Return all Attribute objects on this object. + Return all Attribute objects on this object, regardless of category. Args: accessing_obj (object, optional): Check the `attrread` diff --git a/evennia/typeclasses/tags.py b/evennia/typeclasses/tags.py index 488dce0f85..ea675366fd 100644 --- a/evennia/typeclasses/tags.py +++ b/evennia/typeclasses/tags.py @@ -345,13 +345,14 @@ class TagHandler(object): self._catcache = {} self._cache_complete = False - def all(self, return_key_and_category=False): + def all(self, return_key_and_category=False, return_objs=False): """ Get all tags in this handler, regardless of category. Args: return_key_and_category (bool, optional): Return a list of tuples `[(key, category), ...]`. + return_objs (bool, optional): Return tag objects. Returns: tags (list): A list of tag keys `[tagkey, tagkey, ...]` or @@ -365,6 +366,8 @@ class TagHandler(object): if return_key_and_category: # return tuple (key, category) return [(to_str(tag.db_key), to_str(tag.db_category)) for tag in tags] + elif return_objs: + return tags else: return [to_str(tag.db_key) for tag in tags]