From 8e020bfb621186a965f5543074acd13a702fff55 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 10 Jan 2015 17:56:33 +0100 Subject: [PATCH] Moved dummyrunner under bin/. --- bin/evennia | 31 +- .../dummyrunner => bin/testing}/README.txt | 0 .../dummyrunner => bin/testing}/__init__.py | 0 .../testing}/dummyrunner.py | 261 ++++++++--------- bin/testing/dummyrunner_settings.py | 264 ++++++++++++++++++ .../dummyrunner => bin/testing}/memplot.py | 0 .../testing}/test_queries.py | 0 evennia/settings_default.py | 3 + .../utils/dummyrunner/dummyrunner_actions.py | 231 --------------- evennia/utils/utils.py | 2 +- 10 files changed, 415 insertions(+), 377 deletions(-) rename {evennia/utils/dummyrunner => bin/testing}/README.txt (100%) rename {evennia/utils/dummyrunner => bin/testing}/__init__.py (100%) rename {evennia/utils/dummyrunner => bin/testing}/dummyrunner.py (52%) create mode 100644 bin/testing/dummyrunner_settings.py rename {evennia/utils/dummyrunner => bin/testing}/memplot.py (100%) rename {evennia/utils/dummyrunner => bin/testing}/test_queries.py (100%) delete mode 100644 evennia/utils/dummyrunner/dummyrunner_actions.py diff --git a/bin/evennia b/bin/evennia index 8d6c1daa9c..6b31ecf39c 100755 --- a/bin/evennia +++ b/bin/evennia @@ -15,7 +15,7 @@ import signal import shutil import importlib from argparse import ArgumentParser -from subprocess import Popen, check_output +from subprocess import Popen, check_output, call import django # Signal processing @@ -27,6 +27,8 @@ EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin") EVENNIA_LIB = os.path.join(EVENNIA_ROOT, "evennia") EVENNIA_RUNNER = os.path.join(EVENNIA_BIN, "evennia_runner.py") EVENNIA_TEMPLATE = os.path.join(EVENNIA_ROOT, "game_template") +EVENNIA_BINTESTING = os.path.join(EVENNIA_BIN, "testing") +EVENNIA_DUMMYRUNNER = os.path.join(EVENNIA_BINTESTING, "dummyrunner.py") TWISTED_BINARY = "twistd" @@ -675,6 +677,22 @@ def init_game_directory(path): print INFO_WINDOWS_BATFILE.format(twistd_path=twistd_path) +def run_dummyrunner(number_of_dummies): + """ + Start an instance of the dummyrunner + + The dummy players' behavior can be customized by adding a + dummyrunner_settings.py config file in the game's conf directory. + """ + number_of_dummies = str(int(number_of_dummies)) if number_of_dummies else 1 + cmdstr = [sys.executable, EVENNIA_DUMMYRUNNER, "-N", number_of_dummies] + config_file = os.path.join(SETTINGS_PATH, "dummyrunner_settings.py") + if os.path.exists(config_file): + cmdstr.extend(["--config", config_file]) + try: + call(cmdstr, env=getenv()) + except KeyboardInterrupt: + pass def run_menu(): """ @@ -834,8 +852,10 @@ def main(): help="Start given processes in interactive mode.") parser.add_argument('--init', action='store', dest="init", metavar="name", help="Creates a new game directory 'name' at the current location.") - parser.add_argument('-p', '--prof', action='store_true', dest='profiler', default=False, + parser.add_argument('--profile', action='store_true', dest='profiler', default=False, help="Start given server component under the Python profiler.") + parser.add_argument('--dummyrunner', nargs=1, action='store', dest='dummyrunner', metavar="N", + help="Tests a running server by connecting N dummy players to it.") parser.add_argument("mode", metavar="option", nargs='?', default="help", help="Operational mode or management option. Commonly start, stop, reload, migrate, or menu (default).") parser.add_argument("service", metavar="component", nargs='?', choices=["all", "server", "portal"], default="all", @@ -858,7 +878,7 @@ def main(): if args.show_version: print show_version_info(mode=="help") sys.exit() - if mode == "help": + if mode == "help" and not args.dummyrunner: print ABOUT_INFO sys.exit() @@ -866,7 +886,10 @@ def main(): # and initializes django for the game directory init_game_directory(CURRENT_DIR) - if mode == 'menu': + if args.dummyrunner: + # launch the dummy runner + run_dummyrunner(args.dummyrunner[0]) + elif mode == 'menu': # launch menu for operation run_menu() elif mode in ('start', 'reload', 'stop'): diff --git a/evennia/utils/dummyrunner/README.txt b/bin/testing/README.txt similarity index 100% rename from evennia/utils/dummyrunner/README.txt rename to bin/testing/README.txt diff --git a/evennia/utils/dummyrunner/__init__.py b/bin/testing/__init__.py similarity index 100% rename from evennia/utils/dummyrunner/__init__.py rename to bin/testing/__init__.py diff --git a/evennia/utils/dummyrunner/dummyrunner.py b/bin/testing/dummyrunner.py similarity index 52% rename from evennia/utils/dummyrunner/dummyrunner.py rename to bin/testing/dummyrunner.py index 8aec9f7607..fd93d45dc3 100644 --- a/evennia/utils/dummyrunner/dummyrunner.py +++ b/bin/testing/dummyrunner.py @@ -31,36 +31,56 @@ for instructions on how to define this module. """ -import os, sys, time, random -from optparse import OptionParser +import time, random +from argparse import ArgumentParser from twisted.conch import telnet from twisted.internet import reactor, protocol -# from twisted.application import internet, service -# from twisted.web import client from twisted.internet.task import LoopingCall -# Tack on the root evennia directory to the python path and initialize django settings - -#TODO -#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))) -#os.environ["DJANGO_SETTINGS_MODULE"] = "game.settings" - -#from game import settings -#try: -# from django.conf import settings as settings2 -# settings2.configure() -#except RuntimeError: -# pass -#finally: -# del settings2 - from django.conf import settings -from evennia.utils import utils +from evennia.utils import mod_import + +# Load the dummyrunner settings module + +DUMMYRUNNER_SETTINGS = mod_import(settings.DUMMYRUNNER_SETTINGS_MODULE) +DATESTRING = "%Y%m%d%H%M%S" + +# Settings + +# number of clients to launch if no input is given on command line +NCLIENTS = 1 +# time between each 'tick', in seconds, if not set on command +# line. All launched clients will be called upon to possibly do an +# action with this frequency. +TIMESTEP = DUMMYRUNNER_SETTINGS.TIMESTEP +# chance of a client performing an action, per timestep. This helps to +# spread out usage randomly, like it would be in reality. +CHANCE_OF_ACTION = DUMMYRUNNER_SETTINGS.CHANCE_OF_ACTION +# spread out the login action separately, having many players create accounts +# and connect simultaneously is generally unlikely. +CHANCE_OF_LOGIN = DUMMYRUNNER_SETTINGS.CHANCE_OF_LOGIN +# Port to use, if not specified on command line +TELNET_PORT = DUMMYRUNNER_SETTINGS.TELNET_PORT or settings.TELNET_PORTS[0] +# +NLOGGED_IN = 0 + +# Messages + +INFO_STARTING = \ + """ + Dummyrunner starting, {N} dummy player(s). + + Use Ctrl-C to stop/disconnect clients. + """ + +ERROR_FEW_ACTIONS = \ +""" +Dummyrunner error: The ACTIONS tuple is too short: it must contain at +least login- and logout functions. +""" + HELPTEXT = """ - -Usage: dummyrunner.py [-h][-v][-V] [nclients] - DO NOT RUN THIS ON A PRODUCTION SERVER! USE A CLEAN/TESTING DATABASE! This stand-alone program launches dummy telnet clients against a @@ -81,60 +101,44 @@ Setup: PERMISSION_PLAYER_DEFAULT="Builders" - 3a) Start Evennia like normal. - 3b) If you want profiling, start Evennia like this instead: + You can also customize the dummyrunner by modifying + a setting file specified by DUMMYRUNNER_SETTINGS_MODULE - python runner.py -S start + 3) Start Evennia like normal, optionally with profiling (--profile) + 4) run this dummy runner via the evennia launcher: - this will start Evennia under cProfiler with output server.prof. - 4) run this dummy runner: - - python dummyclients.py [timestep] [port] - - Default is to connect one client to port 4000, using a 5 second - timestep. Increase the number of clients and shorten the - timestep (minimum is 1s) to further stress the game. - - You can stop the dummy runner with Ctrl-C. + evennia --dummyrunner 5) Log on and determine if game remains responsive despite the heavier load. Note that if you do profiling, there is an additional overhead from the profiler too! 6) If you use profiling, let the game run long enough to gather - data, then stop the server. You can inspect the server.prof file - from a python prompt (see Python's manual on cProfiler). + data, then stop the server, ideally from inside it with + @shutdown. You can inspect the server.prof file from a python + prompt (see Python's manual on cProfiler). """ -# number of clients to launch if no input is given on command line -DEFAULT_NCLIENTS = 1 -# time between each 'tick', in seconds, if not set on command -# line. All launched clients will be called upon to possibly do an -# action with this frequency. -DEFAULT_TIMESTEP = 2 -# chance of a client performing an action, per timestep. This helps to -# spread out usage randomly, like it would be in reality. -CHANCE_OF_ACTION = 0.05 -# spread out the login action separately, having many players create accounts -# and connect simultaneously is generally unlikely. -CHANCE_OF_LOGIN = 0.5 -# Port to use, if not specified on command line -DEFAULT_PORT = settings.TELNET_PORTS[0] -# -NLOGGED_IN = 0 -NCLIENTS = 0 #------------------------------------------------------------ # Helper functions #------------------------------------------------------------ + +ICOUNT = 0 def idcounter(): - "generates subsequent id numbers" - idcount = 0 - while True: - idcount += 1 - yield idcount -OID = idcounter() -CID = idcounter() + "makes unique ids" + global ICOUNT + ICOUNT += 1 + return str(ICOUNT) + + +GCOUNT = 0 +def gidcounter(): + "makes globally unique ids" + global GCOUNT + GCOUNT += 1 + return "%s-%s" % (time.strftime(DATESTRING), ICOUNT) + def makeiter(obj): "makes everything iterable" @@ -156,35 +160,38 @@ class DummyClient(telnet.StatefulTelnetProtocol): def connectionMade(self): # public properties - self.cid = CID.next() + self.cid = idcounter() + self.key = "Dummy-%s" % self.cid + self.gid = "%s-%s" % (time.strftime(DATESTRING), self.cid) self.istep = 0 + self.loggedin = False self.exits = [] # exit names created self.objs = [] # obj names created self._report = "" self._cmdlist = [] # already stepping in a cmd definition - self._ncmds = 0 - self._actions = self.factory.actions - self._echo_brief = self.factory.verbose == 1 - self._echo_all = self.factory.verbose == 2 - #print " ** client %i connected." % self.cid + nactions = len(self.factory.actions) # this has already been normalized + if nactions < 2: + raise RuntimeError(ERROR_FEW_ACTIONS) + self._login = self.factory.actions[0] + self._logout = self.factory.actions[1] + self._actions = self.factory.actions[2:] reactor.addSystemEventTrigger('before', 'shutdown', self.logout) # start client tick d = LoopingCall(self.step) # dissipate exact step by up to +/- 0.5 second - timestep = self.factory.timestep + (-0.5 + (random.random()*1.0)) + timestep = TIMESTEP + (-0.5 + (random.random()*1.0)) d.start(timestep, now=True).addErrback(self.error) + def dataReceived(self, data): "Echo incoming data to stdout" - if self._echo_all: - print data + pass def connectionLost(self, reason): "loosing the connection" - #print " ** client %i lost connection." % self.cid def error(self, err): "error callback" @@ -192,12 +199,12 @@ class DummyClient(telnet.StatefulTelnetProtocol): def counter(self): "produces a unique id, also between clients" - return OID.next() + return gidcounter() def logout(self): "Causes the client to log out of the server. Triggered by ctrl-c signal." - cmd, report = self._actions[1](self) - print "client %i %s (%s actions)" % (self.cid, report, self.istep) + cmd = self._logout(self) + print "client %s(%s) logout (%s actions)" % (self.key, self.cid, self.istep) self.sendLine(cmd) def step(self): @@ -206,52 +213,49 @@ class DummyClient(telnet.StatefulTelnetProtocol): and causes the client to issue commands to the server. This holds all "intelligence" of the dummy client. """ - if self.istep == 0 and random.random() > CHANCE_OF_LOGIN: - return - elif random.random() > CHANCE_OF_ACTION: - return - global NLOGGED_IN + + rand = random.random() + if not self._cmdlist: - # no cmdlist in store, get a new one - if self.istep == 0: - NLOGGED_IN += 1 - cfunc = self._actions[0] - else: # random selection using cumulative probabilities - rand = random.random() - cfunc = [func for cprob, func in self._actions[2] if cprob >= rand][0] - # assign to internal cmdlist - cmd, self._report = cfunc(self) - self._cmdlist = list(makeiter(cmd)) - self._ncmds = len(self._cmdlist) - # output - if self.istep == 0 and not (self._echo_brief or self._echo_all): - # only print login - print "client %i %s (%i/%i)" % (self.cid, self._report, NLOGGED_IN, NCLIENTS) - elif self.istep == 0 or self._echo_brief or self._echo_all: - print "client %i %s (%i/%i)" % (self.cid, self._report, self._ncmds-(len(self._cmdlist)-1), self._ncmds) - # launch the action by popping the first element from cmdlist (don't hide tracebacks) - self.sendLine(str(self._cmdlist.pop(0))) - self.istep += 1 # only steps up if an action is taken + # no commands ready. Load some. + + if not self.loggedin: + if rand < CHANCE_OF_LOGIN: + # get the login commands + self._cmdlist = list(makeiter(self._login(self))) + NLOGGED_IN += 1 # this is for book-keeping + print "connecting client %s (%i/%i)..." % (self.key, NLOGGED_IN, NCLIENTS) + self.loggedin = True + else: + # we always pick a cumulatively random function + crand = random.random() + cfunc = [func for cprob, func in self._actions if cprob >= crand][0] + self._cmdlist = list(makeiter(cfunc(self))) + + # at this point we always have a list of commands + if rand < CHANCE_OF_ACTION: + # send to the game + self.sendLine(str(self._cmdlist.pop(0))) + self.istep += 1 + class DummyFactory(protocol.ClientFactory): protocol = DummyClient - def __init__(self, actions, timestep, verbose): + def __init__(self, actions): "Setup the factory base (shared by all clients)" self.actions = actions - self.timestep = timestep - self.verbose = verbose #------------------------------------------------------------ # Access method: # Starts clients and connects them to a running server. #------------------------------------------------------------ -def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, verbose=0): +def start_all_dummy_clients(nclients): - # validating and preparing the action tuple global NCLIENTS - NCLIENTS = nclients + NCLIENTS = int(nclients) + actions = DUMMYRUNNER_SETTINGS.ACTIONS # make sure the probabilities add up to 1 pratio = 1.0 / sum(tup[0] for tup in actions[2:]) @@ -262,9 +266,9 @@ def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, v actions = (flogin, flogout, zip(cprobs, cfuncs)) # setting up all clients (they are automatically started) - factory = DummyFactory(actions, timestep, verbose) - for i in range(nclients): - reactor.connectTCP("localhost", telnet_port, factory) + factory = DummyFactory(actions) + for i in range(NCLIENTS): + reactor.connectTCP("localhost", TELNET_PORT, factory) # start reactor reactor.run() @@ -275,39 +279,14 @@ def start_all_dummy_clients(actions, nclients=1, timestep=5, telnet_port=4000, v if __name__ == '__main__': # parsing command line with default vals - parser = OptionParser(usage="%prog [options] [timestep, [port]]", - description="This program requires some preparations to run properly. Start it without any arguments or options for full help.") - parser.add_option('-v', '--verbose', action='store_const', const=1, dest='verbose', - default=0,help="echo brief description of what clients do every timestep.") - parser.add_option('-V', '--very-verbose', action='store_const',const=2, dest='verbose', - default=0,help="echo all client returns to stdout (hint: use only with nclients=1!)") + parser = ArgumentParser(description=HELPTEXT) + parser.add_argument("-N", nargs=1, default=1, dest="nclients", + help="Number of clients to start") - options, args = parser.parse_args() + args = parser.parse_args() - nargs = len(args) - nclients = DEFAULT_NCLIENTS - timestep = DEFAULT_TIMESTEP - port = DEFAULT_PORT - try: - if not args : raise Exception - if nargs > 0: nclients = max(1, int(args[0])) - if nargs > 1: timestep = max(1, int(args[1])) - if nargs > 2: port = int(args[2]) - except Exception: - print HELPTEXT - sys.exit() - - # import the ACTION tuple from a given module - try: - action_modpath = settings.DUMMYRUNNER_ACTIONS_MODULE - except AttributeError: - # use default - action_modpath = "evennia.utils.dummyrunner.dummyrunner_actions" - actions = utils.variable_from_module(action_modpath, "ACTIONS") - - print "Connecting %i dummy client(s) to port %i using a %i second timestep ... " % (nclients, port, timestep) + print INFO_STARTING.format(N=args.nclients[0]) t0 = time.time() - start_all_dummy_clients(actions, nclients, timestep, port, - verbose=options.verbose) + start_all_dummy_clients(nclients=args.nclients[0]) ttot = time.time() - t0 - print "... dummy client runner finished after %i seconds." % ttot + print "... dummy client runner stopped after %i seconds." % ttot diff --git a/bin/testing/dummyrunner_settings.py b/bin/testing/dummyrunner_settings.py new file mode 100644 index 0000000000..a9b16a8d95 --- /dev/null +++ b/bin/testing/dummyrunner_settings.py @@ -0,0 +1,264 @@ +""" +Settings and actions for the dummyrunner + +This module defines dummyrunner settings and sets up +the actions available to dummy players. + +The settings are global variables: + +TIMESTEP - time in seconds between each 'tick' +CHANCE_OF_ACTION - chance 0-1 of action happening +CHANCE_OF_LOGIN - chance 0-1 of login happening +TELNET_PORT - port to use, defaults to settings.TELNET_PORT +ACTIONS - see below + +ACTIONS is a tuple + +(login_func, logout_func, (0.3, func1), (0.1, func2) ... ) + +where the first entry is the function to call on first connect, with a +chance of occurring given by CHANCE_OF_LOGIN. This function is usually +responsible for logging in the player. The second entry is always +called when the dummyrunner disconnects from the server and should +thus issue a logout command. The other entries are tuples (chance, +func). They are picked randomly, their commonality based on the +cumulative chance given (the chance is normalized between all options +so if will still work also if the given chances don't add up to 1). +Since each function can return a list of game-command strings, each +function may result in multiple operations. + +An action-function is called with a "client" argument which is a +reference to the dummy client currently performing the action. It +returns a string or a list of command strings to execute. Use the +client object for optionally saving data between actions. + +The client object has the following relevant properties and methods: + key - an optional client key. This is only used for dummyrunner output. + Default is "Dummy-" + cid - client id + gid - globally unique id, hashed with time stamp + istep - the current step + exits - an empty list. Can be used to store exit names + objs - an empty list. Can be used to store object names + counter() - returns a unique increasing id, hashed with time stamp + to make it unique also between dummyrunner instances. + +The return should either be a single command string or a tuple of +command strings. This list of commands will always be executed every +TIMESTEP with a chance given by CHANCE_OF_ACTION by in the order given +(no randomness) and allows for setting up a more complex chain of +commands (such as creating an account and logging in). + +""" +# Dummy runner settings + +# Time between each dummyrunner "tick", in seconds. Each dummy +# will be called with this frequency. +TIMESTEP = 2 + +# Chance of a dummy actually performing an action on a given tick. +# This spreads out usage randomly, like it would be in reality. +CHANCE_OF_ACTION = 0.05 + +# Chance of a currently unlogged-in dummy performing its login +# action every tick. This emulates not all players logging in +# at exactly the same time. +CHANCE_OF_LOGIN = 0.33 + +# Which telnet port to connect to. If set to None, uses the first +# default telnet port of the running server. +TELNET_PORT = None + + +# Setup actions tuple + +# some convenient templates + +DUMMY_NAME = "Dummy-%s" +DUMMY_PWD = "password-%s" +START_ROOM = "testing_room_start_%s" +ROOM_TEMPLATE = "testing_room_%s" +EXIT_TEMPLATE = "exit_%s" +OBJ_TEMPLATE = "testing_obj_%s" +TOBJ_TEMPLATE = "testing_button_%s" +TOBJ_TYPECLASS = "contrib.tutorial_examples.red_button.RedButton" + + +# action function definitions (pick and choose from +# these to build a client "usage profile" + +# login/logout + +def c_login(client): + "logins to the game" + # we always use a new client name + cname = DUMMY_NAME % client.gid + cpwd = DUMMY_PWD % client.gid + + # set up for digging a first room (to move to and keep the + # login room clean) + roomname = ROOM_TEMPLATE % client.counter() + exitname1 = EXIT_TEMPLATE % client.counter() + exitname2 = EXIT_TEMPLATE % client.counter() + client.exits.extend([exitname1, exitname2]) + + cmds = ('create %s %s' % (cname, cpwd), + 'connect %s %s' % (cname, cpwd), + '@dig %s' % START_ROOM % client.cid, + '@teleport %s' % START_ROOM % client.cid, + '@dig %s = %s, %s' % (roomname, exitname1, exitname2) + ) + return cmds + +def c_login_nodig(client): + "logins, don't dig its own room" + cname = DUMMY_NAME % client.gid + cpwd = DUMMY_PWD % client.gid + + cmds = ('create %s %s' % (cname, cpwd), + 'connect %s %s' % (cname, cpwd)) + return cmds + +def c_logout(client): + "logouts of the game" + return "@quit" + +# random commands + +def c_looks(client): + "looks at various objects" + cmds = ["look %s" % obj for obj in client.objs] + if not cmds: + cmds = ["look %s" % exi for exi in client.exits] + if not cmds: + cmds = "look" + return cmds + +def c_examines(client): + "examines various objects" + cmds = ["examine %s" % obj for obj in client.objs] + if not cmds: + cmds = ["examine %s" % exi for exi in client.exits] + if not cmds: + cmds = "examine me" + return cmds + +def c_help(client): + "reads help files" + cmds = ('help', + 'help @teleport', + 'help look', + 'help @tunnel', + 'help @dig') + return cmds + +def c_digs(client): + "digs a new room, storing exit names on client" + roomname = ROOM_TEMPLATE % client.counter() + exitname1 = EXIT_TEMPLATE % client.counter() + exitname2 = EXIT_TEMPLATE % client.counter() + client.exits.extend([exitname1, exitname2]) + return '@dig/tel %s = %s, %s' % (roomname, exitname1, exitname2) + +def c_creates_obj(client): + "creates normal objects, storing their name on client" + objname = OBJ_TEMPLATE % client.counter() + client.objs.append(objname) + cmds = ('@create %s' % objname, + '@desc %s = "this is a test object' % objname, + '@set %s/testattr = this is a test attribute value.' % objname, + '@set %s/testattr2 = this is a second test attribute.' % objname) + return cmds + +def c_creates_button(client): + "creates example button, storing name on client" + objname = TOBJ_TEMPLATE % client.counter() + client.objs.append(objname) + cmds = ('@create %s:%s' % (objname, TOBJ_TYPECLASS), + '@desc %s = test red button!' % objname) + return cmds + +def c_socialize(client): + "socializechats on channel" + cmds = ('ooc Hello!', + 'ooc Testing ...', + 'ooc Testing ... times 2', + 'say Yo!', + 'emote stands looking around.') + return cmds + +def c_moves(client): + "moves to a previously created room, using the stored exits" + cmds = client.exits # try all exits - finally one will work + return "look" if not cmds else cmds + +def c_moves_n(client): + "move through north exit if available" + return "north" + +def c_moves_s(client): + "move through south exit if available" + return "south" + +# Action tuple (required) +# +# This is a tuple of client action functions. The first element is the +# function the client should use to log into the game and move to +# STARTROOM . The second element is the logout command, for cleanly +# exiting the mud. The following elements are 2-tuples of (probability, +# action_function). The probablities should normally sum up to 1, +# otherwise the system will normalize them. +# + +## "normal builder" definitionj +#ACTIONS = ( c_login, +# c_logout, +# (0.5, c_looks), +# (0.08, c_examines), +# (0.1, c_help), +# (0.01, c_digs), +# (0.01, c_creates_obj), +# (0.3, c_moves)) +## "heavy" builder definition +#ACTIONS = ( c_login, +# c_logout, +# (0.2, c_looks), +# (0.1, c_examines), +# (0.2, c_help), +# (0.1, c_digs), +# (0.1, c_creates_obj), +# #(0.01, c_creates_button), +# (0.2, c_moves)) +## "passive player" definition +#ACTIONS = ( c_login, +# c_logout, +# (0.7, c_looks), +# #(0.1, c_examines), +# (0.3, c_help)) +# #(0.1, c_digs), +# #(0.1, c_creates_obj), +# #(0.1, c_creates_button), +# #(0.4, c_moves)) +## "normal player" definition +ACTIONS = ( c_login, + c_logout, + (0.01, c_digs), + (0.39, c_looks), + (0.2, c_help), + (0.4, c_moves)) +#ACTIONS = (c_login_nodig, +# c_logout, +# (1.0, c_moves_n)) +## "socializing heavy builder" definition +#ACTIONS = (c_login, +# c_logout, +# (0.1, c_socialize), +# (0.1, c_looks), +# (0.2, c_help), +# (0.1, c_creates_obj), +# (0.2, c_digs), +# (0.3, c_moves)) +## "heavy digger memory tester" definition +#ACTIONS = (c_login, +# c_logout, +# (1.0, c_digs)) diff --git a/evennia/utils/dummyrunner/memplot.py b/bin/testing/memplot.py similarity index 100% rename from evennia/utils/dummyrunner/memplot.py rename to bin/testing/memplot.py diff --git a/evennia/utils/dummyrunner/test_queries.py b/bin/testing/test_queries.py similarity index 100% rename from evennia/utils/dummyrunner/test_queries.py rename to bin/testing/test_queries.py diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 5a194afa4a..d35caf38e8 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -243,6 +243,9 @@ LOCK_FUNC_MODULES = ("evennia.locks.lockfuncs", "server.conf.lockfuncs",) # and expansion of which hooks OOB protocols are allowed to call on the server # protocols for attaching tracker hooks for when various object field change OOB_PLUGIN_MODULES = ["evennia.server.oob_cmds", "server.conf.oobfuncs"] +# Module holding settings/actions for the dummyrunner program (see the +# dummyrunner for more information) +DUMMYRUNNER_SETTINGS_MODULE = os.path.join(ROOT_DIR, "bin/testing/dummyrunner_settings") ###################################################################### # Default command sets diff --git a/evennia/utils/dummyrunner/dummyrunner_actions.py b/evennia/utils/dummyrunner/dummyrunner_actions.py deleted file mode 100644 index 3aba1f9b73..0000000000 --- a/evennia/utils/dummyrunner/dummyrunner_actions.py +++ /dev/null @@ -1,231 +0,0 @@ -""" -These are actions for the dummy client runner, using -the default command set and intended for unmodified Evennia. - -Each client action is defined as a function. The clients -will perform these actions randomly (except the login action). - -Each action-definition function should take one argument- "client", -which is a reference to the client currently performing the action -Use the client object for saving data between actions. - -The client object has the following relevant properties and methods: - cid - unique client id - istep - the current step - exits - an empty list. Can be used to store exit names - objs - an empty list. Can be used to store object names - counter() - get an integer value. This counts up for every call and - is always unique between clients. - -The action-definition function should return the command that the -client should send to the server (as if it was input in a mud client). -It should also return a string detailing the action taken. This string is -used by the "brief verbose" mode of the runner and is prepended by -"Client N " to produce output like "Client 3 is creating objects ..." - -This module *must* also define a variable named ACTIONS. This is a tuple -where the first element is the function object for the action function -to call when the client logs onto the server. The following elements -are 2-tuples (probability, action_func), where probability defines how -common it is for that particular action to happen. The runner will -randomly pick between those functions based on the probability. - -ACTIONS = (login_func, (0.3, func1), (0.1, func2) ... ) - -To change the runner to use your custom ACTION and/or action -definitions, edit settings.py and add - - DUMMYRUNNER_ACTIONS_MODULE = "path.to.your.module" - -""" - -# it's very useful to have a unique id for this run to avoid any risk -# of clashes - -import time -RUNID = time.time() - -# some convenient templates - -START_ROOM = "testing_room_start-%s-%s" % (RUNID, "%i") -ROOM_TEMPLATE = "testing_room_%s-%s" % (RUNID, "%i") -EXIT_TEMPLATE = "exit_%s-%s" % (RUNID, "%i") -OBJ_TEMPLATE = "testing_obj_%s-%s" % (RUNID, "%i") -TOBJ_TEMPLATE = "testing_button_%s-%s" % (RUNID, "%i") -TOBJ_TYPECLASS = "examples.red_button.RedButton" - -# action function definitions - -def c_login(client): - "logins to the game" - cname = "Dummy-%s-%i" % (RUNID, client.cid) - #cemail = "%s@dummy.com" % (cname.lower()) - cpwd = "%s-%s" % (RUNID, client.cid) - # set up for digging a first room (to move to) - roomname = ROOM_TEMPLATE % client.counter() - exitname1 = EXIT_TEMPLATE % client.counter() - exitname2 = EXIT_TEMPLATE % client.counter() - client.exits.extend([exitname1, exitname2]) - #cmd = '@dig %s = %s, %s' % (roomname, exitname1, exitname2) - cmd = ('create %s %s' % (cname, cpwd), - 'connect %s %s' % (cname, cpwd), - '@dig %s' % START_ROOM % client.cid, - '@teleport %s' % START_ROOM % client.cid, - '@dig %s = %s, %s' % (roomname, exitname1, exitname2) - ) - - return cmd, "logs in as %s ..." % cname - -def c_login_nodig(client): - "logins, don't dig its own room" - cname = "Dummy-%s-%i" % (RUNID, client.cid) - cpwd = "%s-%s" % (RUNID, client.cid) - cmd = ('create %s %s' % (cname, cpwd), - 'connect %s %s' % (cname, cpwd)) - return cmd, "logs in as %s ..." % cname - -def c_logout(client): - "logouts of the game" - return "@quit", "logs out" - -def c_looks(client): - "looks at various objects" - cmd = ["look %s" % obj for obj in client.objs] - if not cmd: - cmd = ["look %s" % exi for exi in client.exits] - if not cmd: - cmd = "look" - return cmd, "looks ..." - -def c_examines(client): - "examines various objects" - cmd = ["examine %s" % obj for obj in client.objs] - if not cmd: - cmd = ["examine %s" % exi for exi in client.exits] - if not cmd: - cmd = "examine me" - return cmd, "examines objs ..." - -def c_help(client): - "reads help files" - cmd = ('help', - 'help @teleport', - 'help look', - 'help @tunnel', - 'help @dig') - return cmd, "reads help ..." - -def c_digs(client): - "digs a new room, storing exit names on client" - roomname = ROOM_TEMPLATE % client.counter() - exitname1 = EXIT_TEMPLATE % client.counter() - exitname2 = EXIT_TEMPLATE % client.counter() - client.exits.extend([exitname1, exitname2]) - cmd = '@dig/tel %s = %s, %s' % (roomname, exitname1, exitname2) - return cmd, "digs ..." - -def c_creates_obj(client): - "creates normal objects, storing their name on client" - objname = OBJ_TEMPLATE % client.counter() - client.objs.append(objname) - cmd = ('@create %s' % objname, - '@desc %s = "this is a test object' % objname, - '@set %s/testattr = this is a test attribute value.' % objname, - '@set %s/testattr2 = this is a second test attribute.' % objname) - return cmd, "creates obj ..." - -def c_creates_button(client): - "creates example button, storing name on client" - objname = TOBJ_TEMPLATE % client.counter() - client.objs.append(objname) - cmd = ('@create %s:%s' % (objname, TOBJ_TYPECLASS), - '@desc %s = test red button!' % objname) - return cmd, "creates button ..." - -def c_socialize(client): - "socializechats on channel" - cmd = ('ooc Hello!', - 'ooc Testing ...', - 'ooc Testing ... times 2', - 'say Yo!', - 'emote stands looking around.') - return cmd, "socializes ..." - -def c_moves(client): - "moves to a previously created room, using the stored exits" - cmd = client.exits # try all exits - finally one will work - if not cmd: cmd = "look" - return cmd, "moves ..." - -def c_moves_n(client): - "move through north exit if available" - cmd = ("north",) - return cmd, "moves n..." - -def c_moves_s(client): - "move through north exit if available" - cmd = ("north",) - return cmd, "moves s..." - -# Action tuple (required) -# -# This is a tuple of client action functions. The first element is the -# function the client should use to log into the game and move to -# STARTROOM . The second element is the logout command, for cleanly -# exiting the mud. The following elements are 2-tuples of (probability, -# action_function). The probablities should normally sum up to 1, -# otherwise the system will normalize them. -# - -## "normal builder" definitionj -#ACTIONS = ( c_login, -# c_logout, -# (0.5, c_looks), -# (0.08, c_examines), -# (0.1, c_help), -# (0.01, c_digs), -# (0.01, c_creates_obj), -# (0.3, c_moves)) -## "heavy" builder definition -#ACTIONS = ( c_login, -# c_logout, -# (0.2, c_looks), -# (0.1, c_examines), -# (0.2, c_help), -# (0.1, c_digs), -# (0.1, c_creates_obj), -# #(0.01, c_creates_button), -# (0.2, c_moves)) -## "passive player" definition -#ACTIONS = ( c_login, -# c_logout, -# (0.7, c_looks), -# #(0.1, c_examines), -# (0.3, c_help)) -# #(0.1, c_digs), -# #(0.1, c_creates_obj), -# #(0.1, c_creates_button), -# #(0.4, c_moves)) -## "normal player" definition -ACTIONS = ( c_login, - c_logout, - (0.01, c_digs), - (0.39, c_looks), - (0.2, c_help), - (0.4, c_moves)) -#ACTIONS = (c_login_nodig, -# c_logout, -# (1.0, c_moves_n)) -## "socializing heavy builder" definition -#ACTIONS = (c_login, -# c_logout, -# (0.1, c_socialize), -# (0.1, c_looks), -# (0.2, c_help), -# (0.1, c_creates_obj), -# (0.2, c_digs), -# (0.3, c_moves)) -## "heavy digger memory tester" definition -#ACTIONS = (c_login, -# c_logout, -# (1.0, c_digs)) diff --git a/evennia/utils/utils.py b/evennia/utils/utils.py index d2c0896ddc..3919671cce 100644 --- a/evennia/utils/utils.py +++ b/evennia/utils/utils.py @@ -765,7 +765,7 @@ def mod_import(module): module - this can be either a Python path (dot-notation like evennia.objects.models), an absolute path (e.g. /home/eve/evennia/evennia/objects.models.py) - or an already import module object (e.g. models) + or an already imported module object (e.g. models) Returns: an imported module. If the input argument was already a model, this is returned as-is, otherwise the path is parsed and imported.