mirror of
https://github.com/evennia/evennia.git
synced 2026-03-23 00:06:30 +01:00
Refactor all test classes into evennia.utils.test_resources. Update docs.
This commit is contained in:
parent
7912351e01
commit
bbf45af2dd
28 changed files with 528 additions and 588 deletions
|
|
@ -1,6 +1,5 @@
|
|||
# Unit Testing
|
||||
|
||||
|
||||
*Unit testing* means testing components of a program in isolation from each other to make sure every
|
||||
part works on its own before using it with others. Extensive testing helps avoid new updates causing
|
||||
unexpected side effects as well as alleviates general code rot (a more comprehensive wikipedia
|
||||
|
|
@ -31,9 +30,9 @@ how many tests were run and how long it took. If something went wrong you will g
|
|||
If you contribute to Evennia, this is a useful sanity check to see you haven't introduced an
|
||||
unexpected bug.
|
||||
|
||||
## Running tests with custom settings file
|
||||
## Running tests for your game dir
|
||||
|
||||
If you have implemented your own tests for your game (see below) you can run them from your game dir
|
||||
If you have implemented your own tests for your game you can run them from your game dir
|
||||
with
|
||||
|
||||
evennia test .
|
||||
|
|
@ -41,8 +40,8 @@ with
|
|||
The period (`.`) means to run all tests found in the current directory and all subdirectories. You
|
||||
could also specify, say, `typeclasses` or `world` if you wanted to just run tests in those subdirs.
|
||||
|
||||
Those tests will all be run using the default settings. To run the tests with your own settings file
|
||||
you must use the `--settings` option:
|
||||
An important thing to note is that those tests will all be run using the _default Evennia settings_.
|
||||
To run the tests with your own settings file you must use the `--settings` option:
|
||||
|
||||
evennia test --settings settings.py .
|
||||
|
||||
|
|
@ -50,108 +49,184 @@ The `--settings` option of Evennia takes a file name in the `mygame/server/conf`
|
|||
normally used to swap settings files for testing and development. In combination with `test`, it
|
||||
forces Evennia to use this settings file over the default one.
|
||||
|
||||
You can also test specific things by giving their path
|
||||
|
||||
evennia test --settings settings.py .world.tests.YourTest
|
||||
|
||||
|
||||
## Writing new tests
|
||||
|
||||
Evennia's test suite makes use of Django unit test system, which in turn relies on Python's
|
||||
*unittest* module.
|
||||
|
||||
> If you want to help out writing unittests for Evennia, take a look at Evennia's [coveralls.io
|
||||
page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of
|
||||
test coverage and which does not.
|
||||
|
||||
To make the test runner find the tests, they must be put in a module named `test*.py` (so `test.py`,
|
||||
`tests.py` etc). Such a test module will be found wherever it is in the package. It can be a good
|
||||
idea to look at some of Evennia's `tests.py` modules to see how they look.
|
||||
|
||||
Inside a testing file, a `unittest.TestCase` class is used to test a single aspect or component in
|
||||
various ways. Each test case contains one or more *test methods* - these define the actual tests to
|
||||
run. You can name the test methods anything you want as long as the name starts with "`test_`".
|
||||
Your `TestCase` class can also have a method `setUp()`. This is run before each test, setting up and
|
||||
storing whatever preparations the test methods need. Conversely, a `tearDown()` method can
|
||||
optionally do cleanup after each test.
|
||||
Inside the module you need to put a class inheriting (at any distance) from `unittest.TestCase`. Each
|
||||
method on that class that starts with `test_` will be run separately as a unit test. There
|
||||
are two special, optional methods `setUp` and `tearDown` that will (if you define them) run before
|
||||
_every_ test. This can be useful for setting up and deleting things.
|
||||
|
||||
To test the results, you use special methods of the `TestCase` class. Many of those start with
|
||||
"`assert`", such as `assertEqual` or `assertTrue`.
|
||||
To actually test things, you use special `assert...` methods on the class. Most common on is
|
||||
`assertEqual`, which makes sure a result is what you expect it to be.
|
||||
|
||||
Example of a `TestCase` class:
|
||||
Here's an example of the principle. Let's assume you put this in `mygame/world/tests.py`
|
||||
and want to test a function in `mygame/world/myfunctions.py`
|
||||
|
||||
```python
|
||||
# in a module tests.py somewhere i your game dir
|
||||
import unittest
|
||||
|
||||
from evennia import create_object
|
||||
# the function we want to test
|
||||
from mypath import myfunc
|
||||
from .myfunctions import myfunc
|
||||
|
||||
|
||||
class TestObj(unittest.TestCase):
|
||||
"This tests a function myfunc."
|
||||
|
||||
def setUp(self):
|
||||
"""done before every of the test_ * methods below"""
|
||||
self.obj = create_object("mytestobject")
|
||||
|
||||
def tearDown(self):
|
||||
"""done after every test_* method below """
|
||||
self.obj.delete()
|
||||
|
||||
def test_return_value(self):
|
||||
"test method. Makes sure return value is as expected."
|
||||
expected_return = "This is me being nice."
|
||||
actual_return = myfunc()
|
||||
"""test method. Makes sure return value is as expected."""
|
||||
actual_return = myfunc(self.obj)
|
||||
expected_return = "This is the good object 'mytestobject'."
|
||||
# test
|
||||
self.assertEqual(expected_return, actual_return)
|
||||
def test_alternative_call(self):
|
||||
"test method. Calls with a keyword argument."
|
||||
expected_return = "This is me being baaaad."
|
||||
actual_return = myfunc(bad=True)
|
||||
"""test method. Calls with a keyword argument."""
|
||||
actual_return = myfunc(self.obj, bad=True)
|
||||
expected_return = "This is the baaad object 'mytestobject'."
|
||||
# test
|
||||
self.assertEqual(expected_return, actual_return)
|
||||
```
|
||||
|
||||
You might also want to read the [documentation for the unittest
|
||||
module](https://docs.python.org/library/unittest.html).
|
||||
To test this, run
|
||||
|
||||
### Using the EvenniaTest class
|
||||
evennia test --settings settings.py .
|
||||
|
||||
Evennia offers a custom TestCase, the `evennia.utils.test_resources.EvenniaTest` class. This class
|
||||
initiates a range of useful properties on themselves for testing Evennia systems. Examples are
|
||||
`.account` and `.session` representing a mock connected Account and its Session and `.char1` and
|
||||
`char2` representing Characters complete with a location in the test database. These are all useful
|
||||
when testing Evennia system requiring any of the default Evennia typeclasses as inputs. See the full
|
||||
definition of the `EvenniaTest` class in
|
||||
[evennia/utils/test_resources.py](https://github.com/evennia/evennia/blob/master/evennia/utils/test_resources.py).
|
||||
to run the entire test module
|
||||
|
||||
evennia test --settings setings.py .world.tests
|
||||
|
||||
or a specific class:
|
||||
|
||||
evennia test --settings settings.py .world.tests.TestObj
|
||||
|
||||
You can also run a specific test:
|
||||
|
||||
evennia test --settings settings.py .world.tests.TestObj.test_alternative_call
|
||||
|
||||
You might also want to read the [Python documentation for the unittest module](https://docs.python.org/library/unittest.html).
|
||||
|
||||
## Using the Evennia testing classes
|
||||
|
||||
Evennia offers many custom testing classes that helps with testing Evennia features.
|
||||
They are all found in [evennia.utils.test_resources](evennia.utils.test_resources). Note that
|
||||
these classes implement the `setUp` and `tearDown` already, so if you want to add stuff in them
|
||||
yourself you should remember to use e.g. `super().setUp()` in your code.
|
||||
|
||||
### Classes for testing your game dir
|
||||
|
||||
These all use whatever setting you pass to them and works well for testing code in your game dir.
|
||||
|
||||
- `EvenniaTest` - this sets up a full object environment for your test. All the created entities
|
||||
can be accesses as properties on the class:
|
||||
- `.account` - A fake [Account](evennia.accounts.accounts.DefaultAccount) named "TestAccount".
|
||||
- `.account2` - Another account named "TestAccount2"
|
||||
- `char1` - A [Character](evennia.objects.objects.DefaultCharacter) linked to `.account`, named `Char`.
|
||||
This has 'Developer' permissions but is not a superuser.
|
||||
- `.char2` - Another character linked to `account`, named `Char2`. This has base permissions (player).
|
||||
- `.obj1` - A regular [Object](evennia.objects.objects.DefaultObject) named "Obj".
|
||||
- `.obj2` - Another object named "Obj2".
|
||||
- `.room1` - A [Room](evennia.objects.objects.DefaultRoom) named "Room". Both characters and both
|
||||
objects are located inside this room. It has a description of "room_desc".
|
||||
- `.room2` - Another room named "Room2". It is empty and has no set description.
|
||||
- `.exit` - An exit named "out" that leads from `.room1` to `.room2`.
|
||||
- `.script` - A [Script](evennia.scripts.scripts.DefaultScript) named "Script". It's an inert script
|
||||
without a timing component.
|
||||
- `.session` - A fake [Session](evennia.server.serversession.ServerSession) that mimics a player
|
||||
connecting to the game. It is used by `.account1` and has a sessid of 1.
|
||||
- `EvenniaCommandTest` - has the same environment like `EvenniaTest` but also adds a special
|
||||
[.call()](evennia.utils.test_resources.EvenniaCommandTestMixin.call) method specifically for
|
||||
testing Evennia [Commands](Commands.md). It allows you to compare what the command _actually_
|
||||
returns to the player with what you expect. Read the `call` api doc for more info.
|
||||
- `EvenniaTestCase` - This is identical to the regular Python `TestCase` class, it's
|
||||
just there for naming symmetry with `BaseEvenniaTestCase` below.
|
||||
|
||||
Here's an example of using `EvenniaTest`
|
||||
|
||||
```python
|
||||
# in a test module
|
||||
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
|
||||
|
||||
class TestObject(BaseEvenniaTest):
|
||||
def test_object_search(self):
|
||||
# char1 and char2 are both created in room1
|
||||
class TestObject(EvenniaTest):
|
||||
"""Remember that the testing class creates char1 and char2 inside room1 ..."""
|
||||
def test_object_search_character(self):
|
||||
"""Check that char1 can search for char2 by name"""
|
||||
self.assertEqual(self.char1.search(self.char2.key), self.char2)
|
||||
|
||||
def test_location_search(self):
|
||||
"""Check so that char1 can find the current location by name"""
|
||||
self.assertEqual(self.char1.search(self.char1.location.key), self.char1.location)
|
||||
# ...
|
||||
```
|
||||
|
||||
### Testing in-game Commands
|
||||
|
||||
In-game Commands are a special case. Tests for the default commands are put in
|
||||
`evennia/commands/default/tests.py`. This uses a custom `CommandTest` class that inherits from
|
||||
`evennia.utils.test_resources.EvenniaTest` described above. `CommandTest` supplies extra convenience
|
||||
functions for executing commands and check that their return values (calls of `msg()` returns
|
||||
expected values. It uses Characters and Sessions generated on the `EvenniaTest` class to call each
|
||||
class).
|
||||
|
||||
Each command tested should have its own `TestCase` class. Inherit this class from the `CommandTest`
|
||||
class in the same module to get access to the command-specific utilities mentioned.
|
||||
This example tests a custom command.
|
||||
|
||||
```python
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.commands.default import general
|
||||
class TestSet(CommandTest):
|
||||
"tests the look command by simple call, using Char2 as a target"
|
||||
def test_mycmd_char(self):
|
||||
self.call(general.CmdLook(), "Char2", "Char2(#7)")
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from commands import command as mycommand
|
||||
|
||||
|
||||
class TestSet(EvenniaCommandTest):
|
||||
"tests the look command by simple call, using Char2 as a target"
|
||||
|
||||
def test_mycmd_char(self):
|
||||
self.call(mycommand.CmdMyLook(), "Char2", "Char2(#7)")
|
||||
|
||||
def test_mycmd_room(self):
|
||||
"tests the look command by simple call, with target as room"
|
||||
def test_mycmd_room(self):
|
||||
self.call(general.CmdLook(), "Room",
|
||||
"Room(#1)\nroom_desc\nExits: out(#3)\n"
|
||||
"You see: Obj(#4), Obj2(#5), Char2(#7)")
|
||||
self.call(mycommand.CmdMyLook(), "Room",
|
||||
"Room(#1)\nroom_desc\nExits: out(#3)\n"
|
||||
"You see: Obj(#4), Obj2(#5), Char2(#7)")
|
||||
```
|
||||
|
||||
### Unit testing contribs with custom models
|
||||
When using `.call`, you don't need to specify the entire string; you can just give the beginning
|
||||
of it and if it matches, that's enough. Use `\n` to denote line breaks and (this is a special for
|
||||
the `.call` helper), `||` to indicate multiple uses of `.msg()` in the Command. The `.call` helper
|
||||
has a lot of arguments for mimicing different ways of calling a Command, so make sure to
|
||||
[read the API docs for .call()](evennia.utils.test_resources.EvenniaCommandTestMixin.call).
|
||||
|
||||
### Classes for testing Evennia core
|
||||
|
||||
These are used for testing Evennia itself. They provide the same resources as the classes
|
||||
above but enforce Evennias default settings found in `evennia/settings_default.py`, ignoring
|
||||
any settings changes in your game dir.
|
||||
|
||||
- `BaseEvenniaTest` - all the default objects above but with enforced default settings
|
||||
- `BaseEvenniaCommandTest` - for testing Commands, but with enforced default settings
|
||||
- `BaseEvenniaTestCase` - no default objects, only enforced default settings
|
||||
|
||||
There are also two special 'mixin' classes. These are uses in the classes above, but may also
|
||||
be useful if you want to mix your own testing classes:
|
||||
|
||||
- `EvenniaTestMixin` - A class mixin that creates all test environment objects.
|
||||
- `EvenniaCommandMixin` - A class mixin that adds the `.call()` Command-tester helper.
|
||||
|
||||
If you want to help out writing unittests for Evennia, take a look at Evennia's [coveralls.io
|
||||
page](https://coveralls.io/github/evennia/evennia). There you see which modules have any form of
|
||||
test coverage and which does not. All help is appreciated!
|
||||
|
||||
## Unit testing contribs with custom models
|
||||
|
||||
A special case is if you were to create a contribution to go to the `evennia/contrib` folder that
|
||||
uses its [own database models](../Concepts/New-Models.md). The problem with this is that Evennia (and Django) will
|
||||
|
|
@ -216,14 +291,8 @@ class TestMyModel(BaseEvenniaTest):
|
|||
# test case here
|
||||
```
|
||||
|
||||
### A note on adding new tests
|
||||
|
||||
Having an extensive tests suite is very important for avoiding code degradation as Evennia is
|
||||
developed. Only a small fraction of the Evennia codebase is covered by test suites at this point.
|
||||
Writing new tests is not hard, it's more a matter of finding the time to do so. So adding new tests
|
||||
is really an area where everyone can contribute, also with only limited Python skills.
|
||||
|
||||
### A note on making the test runner faster
|
||||
## A note on making the test runner faster
|
||||
|
||||
If you have custom models with a large number of migrations, creating the test database can take a
|
||||
very long time. If you don't require migrations to run for your tests, you can disable them with the
|
||||
|
|
@ -246,156 +315,4 @@ After doing so, you can then run tests without migrations by adding the `--nomig
|
|||
|
||||
```
|
||||
evennia test --settings settings.py --nomigrations .
|
||||
```
|
||||
|
||||
## Testing for Game development (mini-tutorial)
|
||||
|
||||
Unit testing can be of paramount importance to game developers. When starting with a new game, it is
|
||||
recommended to look into unit testing as soon as possible; an already huge game is much harder to
|
||||
write tests for. The benefits of testing a game aren't different from the ones regarding library
|
||||
testing. For example it is easy to introduce bugs that affect previously working code. Testing is
|
||||
there to ensure your project behaves the way it should and continue to do so.
|
||||
|
||||
If you have never used unit testing (with Python or another language), you might want to check the
|
||||
[official Python documentation about unit testing](https://docs.python.org/2/library/unittest.html),
|
||||
particularly the first section dedicated to a basic example.
|
||||
|
||||
### Basic testing using Evennia
|
||||
|
||||
Evennia's test runner can be used to launch tests in your game directory (let's call it 'mygame').
|
||||
Evennia's test runner does a few useful things beyond the normal Python unittest module:
|
||||
|
||||
* It creates and sets up an empty database, with some useful objects (accounts, characters and
|
||||
rooms, among others).
|
||||
* It provides simple ways to test commands, which can be somewhat tricky at times, if not tested
|
||||
properly.
|
||||
|
||||
Therefore, you should use the command-line to execute the test runner, while specifying your own
|
||||
game directories (not the one containing evennia). Go to your game directory (referred as 'mygame'
|
||||
in this section) and execute the test runner:
|
||||
|
||||
evennia --settings settings.py test commands
|
||||
|
||||
This command will execute Evennia's test runner using your own settings file. It will set up a dummy
|
||||
database of your choice and look into the 'commands' package defined in your game directory
|
||||
(`mygame/commands` in this example) to find tests. The test module's name should begin with 'test'
|
||||
and contain one or more `TestCase`. A full example can be found below.
|
||||
|
||||
### A simple example
|
||||
|
||||
In your game directory, go to `commands` and create a new file `tests.py` inside (it could be named
|
||||
anything starting with `test`). We will start by making a test that has nothing to do with Commands,
|
||||
just to show how unit testing works:
|
||||
|
||||
```python
|
||||
# mygame/commands/tests.py
|
||||
|
||||
import unittest
|
||||
|
||||
class TestString(unittest.TestCase):
|
||||
|
||||
"""Unittest for strings (just a basic example)."""
|
||||
|
||||
def test_upper(self):
|
||||
"""Test the upper() str method."""
|
||||
self.assertEqual('foo'.upper(), 'FOO')
|
||||
```
|
||||
|
||||
This example, inspired from the Python documentation, is used to test the 'upper()' method of the
|
||||
'str' class. Not very useful, but it should give you a basic idea of how tests are used.
|
||||
|
||||
Let's execute that test to see if it works.
|
||||
|
||||
> evennia --settings settings.py test commands
|
||||
|
||||
TESTING: Using specified settings file 'server.conf.settings'.
|
||||
|
||||
(Obs: Evennia's full test suite may not pass if the settings are very
|
||||
different from the default. Use 'test .' as arguments to run only tests
|
||||
on the game dir.)
|
||||
|
||||
Creating test database for alias 'default'...
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.001s
|
||||
|
||||
OK
|
||||
Destroying test database for alias 'default'...
|
||||
|
||||
We specified the `commands` package to the evennia test command since that's where we put our test
|
||||
file. In this case we could just as well just said `.` to search all of `mygame` for testing files.
|
||||
If we have a lot of tests it may be useful to test only a single set at a time though. We get an
|
||||
information text telling us we are using our custom settings file (instead of Evennia's default
|
||||
file) and then the test runs. The test passes! Change the "FOO" string to something else in the test
|
||||
to see how it looks when it fails.
|
||||
|
||||
### Testing commands
|
||||
|
||||
```{warning} This is not correct anymore.
|
||||
```
|
||||
|
||||
This section will test the proper execution of the 'abilities' command, as described in the DELETED
|
||||
tutorial to create the 'abilities' command, we will need it to test it.
|
||||
|
||||
Testing commands in Evennia is a bit more complex than the simple testing example we have seen.
|
||||
Luckily, Evennia supplies a special test class to do just that ... we just need to inherit from it
|
||||
and use it properly. This class is called 'CommandTest' and is defined in the
|
||||
'evennia.commands.default.tests' package. To create a test for our 'abilities' command, we just
|
||||
need to create a class that inherits from 'CommandTest' and add methods.
|
||||
|
||||
We could create a new test file for this but for now we just append to the `tests.py` file we
|
||||
already have in `commands` from before.
|
||||
|
||||
```python
|
||||
# bottom of mygame/commands/tests.py
|
||||
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
|
||||
from commands.command import CmdAbilities
|
||||
from typeclasses.characters import Character
|
||||
|
||||
class TestAbilities(CommandTest):
|
||||
|
||||
character_typeclass = Character
|
||||
|
||||
def test_simple(self):
|
||||
self.call(CmdAbilities(), "", "STR: 5, AGI: 4, MAG: 2")
|
||||
```
|
||||
|
||||
* Line 1-4: we do some importing. 'CommandTest' is going to be our base class for our test, so we
|
||||
need it. We also import our command ('CmdAbilities' in this case). Finally we import the
|
||||
'Character' typeclass. We need it, since 'CommandTest' doesn't use 'Character', but
|
||||
'DefaultCharacter', which means the character calling the command won't have the abilities we have
|
||||
written in the 'Character' typeclass.
|
||||
* Line 6-8: that's the body of our test. Here, a single command is tested in an entire class.
|
||||
Default commands are usually grouped by category in a single class. There is no rule, as long as
|
||||
you know where you put your tests. Note that we set the 'character_typeclass' class attribute to
|
||||
Character. As explained above, if you didn't do that, the system would create a 'DefaultCharacter'
|
||||
object, not a 'Character'. You can try to remove line 4 and 8 to see what happens when running the
|
||||
test.
|
||||
* Line 10-11: our unique testing method. Note its name: it should begin by 'test_'. Apart from
|
||||
that, the method is quite simple: it's an instance method (so it takes the 'self' argument) but no
|
||||
other arguments are needed. Line 11 uses the 'call' method, which is defined in 'CommandTest'.
|
||||
It's a useful method that compares a command against an expected result. It would be like comparing
|
||||
two strings with 'assertEqual', but the 'call' method does more things, including testing the
|
||||
command in a realistic way (calling its hooks in the right order, so you don't have to worry about
|
||||
that).
|
||||
|
||||
Line 11 can be understood as: test the 'abilities' command (first parameter), with no argument
|
||||
(second parameter), and check that the character using it receives his/her abilities (third
|
||||
parameter).
|
||||
|
||||
Let's run our new test:
|
||||
|
||||
> evennia --settings settings.py test commands
|
||||
[...]
|
||||
Creating test database for alias 'default'...
|
||||
..
|
||||
----------------------------------------------------------------------
|
||||
Ran 2 tests in 0.156s
|
||||
|
||||
OK
|
||||
Destroying test database for alias 'default'...
|
||||
|
||||
Two tests were executed, since we have kept 'TestString' from last time. In case of failure, you
|
||||
will get much more information to help you fix the bug.
|
||||
```
|
||||
|
|
@ -11,8 +11,6 @@ main test suite started with
|
|||
> python game/manage.py test.
|
||||
|
||||
"""
|
||||
import re
|
||||
import types
|
||||
import datetime
|
||||
from anything import Anything
|
||||
|
||||
|
|
@ -23,7 +21,7 @@ from unittest.mock import patch, Mock, MagicMock
|
|||
|
||||
from evennia import DefaultRoom, DefaultExit, ObjectDB
|
||||
from evennia.commands.default.cmdset_character import CharacterCmdSet
|
||||
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTest
|
||||
from evennia.utils.test_resources import BaseEvenniaTest, BaseEvenniaCommandTest, EvenniaCommandTest # noqa
|
||||
from evennia.commands.default import (
|
||||
help as help_module,
|
||||
general,
|
||||
|
|
@ -40,305 +38,18 @@ from evennia.commands.default.muxcommand import MuxCommand
|
|||
from evennia.commands.command import Command, InterruptCommand
|
||||
from evennia.commands import cmdparser
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.utils import ansi, utils, gametime, create
|
||||
from evennia.utils import utils, gametime, create
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia import search_object
|
||||
from evennia import DefaultObject, DefaultCharacter
|
||||
from evennia.prototypes import prototypes as protlib
|
||||
|
||||
|
||||
# set up signal here since we are not starting the server
|
||||
|
||||
_RE_STRIP_EVMENU = re.compile(r"^\+|-+\+|\+-+|--+|\|(?:\s|$)", re.MULTILINE)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Command testing
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@patch("evennia.server.portal.portal.LoopingCall", new=MagicMock())
|
||||
class CommandTestMixin:
|
||||
"""
|
||||
Mixin to add to a test in order to provide the `.call` helper for
|
||||
testing the execution and returns of a command.
|
||||
|
||||
Tests a Command by running it and comparing what messages it sends with
|
||||
expected values. This tests without actually spinning up the cmdhandler
|
||||
for every test, which is more controlled.
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
from commands.echo import CmdEcho
|
||||
|
||||
class MyCommandTest(EvenniaTest, CommandTestMixin):
|
||||
|
||||
def test_echo(self):
|
||||
'''
|
||||
Test that the echo command really returns
|
||||
what you pass into it.
|
||||
'''
|
||||
self.call(MyCommand(), "hello world!",
|
||||
"You hear your echo: 'Hello world!'")
|
||||
|
||||
"""
|
||||
|
||||
# formatting for .call's error message
|
||||
_ERROR_FORMAT = """
|
||||
=========================== Wanted message ===================================
|
||||
{expected_msg}
|
||||
=========================== Returned message =================================
|
||||
{returned_msg}
|
||||
==============================================================================
|
||||
""".rstrip()
|
||||
|
||||
def call(
|
||||
self,
|
||||
cmdobj,
|
||||
input_args,
|
||||
msg=None,
|
||||
cmdset=None,
|
||||
noansi=True,
|
||||
caller=None,
|
||||
receiver=None,
|
||||
cmdstring=None,
|
||||
obj=None,
|
||||
inputs=None,
|
||||
raw_string=None,
|
||||
):
|
||||
"""
|
||||
Test a command by assigning all the needed properties to a cmdobj and
|
||||
running the sequence. The resulting `.msg` calls will be mocked and
|
||||
the text= calls to them compared to a expected output.
|
||||
|
||||
Args:
|
||||
cmdobj (Command): The command object to use.
|
||||
input_args (str): This should be the full input the Command should
|
||||
see, such as 'look here'. This will become `.args` for the Command
|
||||
instance to parse.
|
||||
msg (str or dict, optional): This is the expected return value(s)
|
||||
returned through `caller.msg(text=...)` calls in the command. If a string, the
|
||||
receiver is controlled with the `receiver` kwarg (defaults to `caller`).
|
||||
If this is a `dict`, it is a mapping
|
||||
`{receiver1: "expected1", receiver2: "expected2",...}` and `receiver` is
|
||||
ignored. The message(s) are compared with the actual messages returned
|
||||
to the receiver(s) as the Command runs. Each check uses `.startswith`,
|
||||
so you can choose to only include the first part of the
|
||||
returned message if that's enough to verify a correct result. EvMenu
|
||||
decorations (like borders) are stripped and should not be included. This
|
||||
should also not include color tags unless `noansi=False`.
|
||||
If the command returns texts in multiple separate `.msg`-
|
||||
calls to a receiver, separate these with `|` if `noansi=True`
|
||||
(default) and `||` if `noansi=False`. If no `msg` is given (`None`),
|
||||
then no automatic comparison will be done.
|
||||
cmdset (str, optional): If given, make `.cmdset` available on the Command
|
||||
instance as it runs. While `.cmdset` is normally available on the
|
||||
Command instance by default, this is usually only used by
|
||||
commands that explicitly operates/displays cmdsets, like
|
||||
`examine`.
|
||||
noansi (str, optional): By default the color tags of the `msg` is
|
||||
ignored, this makes them significant. If unset, `msg` must contain
|
||||
the same color tags as the actual return message.
|
||||
caller (Object or Account, optional): By default `self.char1` is used as the
|
||||
command-caller (the `.caller` property on the Command). This allows to
|
||||
execute with another caller, most commonly an Account.
|
||||
receiver (Object or Account, optional): This is the object to receive the
|
||||
return messages we want to test. By default this is the same as `caller`
|
||||
(which in turn defaults to is `self.char1`). Note that if `msg` is
|
||||
a `dict`, this is ignored since the receiver is already specified there.
|
||||
cmdstring (str, optional): Normally this is the Command's `key`.
|
||||
This allows for tweaking the `.cmdname` property of the
|
||||
Command`. This isb used for commands with multiple aliases,
|
||||
where the command explicitly checs which alias was used to
|
||||
determine its functionality.
|
||||
obj (str, optional): This sets the `.obj` property of the Command - the
|
||||
object on which the Command 'sits'. By default this is the same as `caller`.
|
||||
This can be used for testing on-object Command interactions.
|
||||
inputs (list, optional): A list of strings to pass to functions that pause to
|
||||
take input from the user (normally using `@interactive` and
|
||||
`ret = yield(question)` or `evmenu.get_input`). Each element of the
|
||||
list will be passed into the command as if the user wrote that at the prompt.
|
||||
raw_string (str, optional): Normally the `.raw_string` property is set as
|
||||
a combination of your `key/cmdname` and `input_args`. This allows
|
||||
direct control of what this is, for example for testing edge cases
|
||||
or malformed inputs.
|
||||
|
||||
Returns:
|
||||
str or dict: The message sent to `receiver`, or a dict of
|
||||
`{receiver: "msg", ...}` if multiple are given. This is usually
|
||||
only used with `msg=None` to do the validation externally.
|
||||
|
||||
Raises:
|
||||
AssertionError: If the returns of `.msg` calls (tested with `.startswith`) does not
|
||||
match `expected_input`.
|
||||
|
||||
Notes:
|
||||
As part of the tests, all methods of the Command will be called in
|
||||
the proper order:
|
||||
|
||||
- cmdobj.at_pre_cmd()
|
||||
- cmdobj.parse()
|
||||
- cmdobj.func()
|
||||
- cmdobj.at_post_cmd()
|
||||
|
||||
"""
|
||||
# The `self.char1` is created in the `EvenniaTest` base along with
|
||||
# other helper objects like self.room and self.obj
|
||||
caller = caller if caller else self.char1
|
||||
cmdobj.caller = caller
|
||||
cmdobj.cmdname = cmdstring if cmdstring else cmdobj.key
|
||||
cmdobj.raw_cmdname = cmdobj.cmdname
|
||||
cmdobj.cmdstring = cmdobj.cmdname # deprecated
|
||||
cmdobj.args = input_args
|
||||
cmdobj.cmdset = cmdset
|
||||
cmdobj.session = SESSIONS.session_from_sessid(1)
|
||||
cmdobj.account = self.account
|
||||
cmdobj.raw_string = raw_string if raw_string is not None else cmdobj.key + " " + input_args
|
||||
cmdobj.obj = obj or (caller if caller else self.char1)
|
||||
inputs = inputs or []
|
||||
|
||||
# set up receivers
|
||||
receiver_mapping = {}
|
||||
if isinstance(msg, dict):
|
||||
# a mapping {receiver: msg, ...}
|
||||
receiver_mapping = {recv: str(msg).strip() if msg else None
|
||||
for recv, msg in msg.items()}
|
||||
else:
|
||||
# a single expected string and thus a single receiver (defaults to caller)
|
||||
receiver = receiver if receiver else caller
|
||||
receiver_mapping[receiver] = str(msg).strip() if msg is not None else None
|
||||
|
||||
unmocked_msg_methods = {}
|
||||
for receiver in receiver_mapping:
|
||||
# save the old .msg method so we can get it back
|
||||
# cleanly after the test
|
||||
unmocked_msg_methods[receiver] = receiver.msg
|
||||
# replace normal `.msg` with a mock
|
||||
receiver.msg = Mock()
|
||||
|
||||
# Run the methods of the Command. This mimics what happens in the
|
||||
# cmdhandler. This will have the mocked .msg be called as part of the
|
||||
# execution. Mocks remembers what was sent to them so we will be able
|
||||
# to retrieve what was sent later.
|
||||
try:
|
||||
if cmdobj.at_pre_cmd():
|
||||
return
|
||||
cmdobj.parse()
|
||||
ret = cmdobj.func()
|
||||
|
||||
# handle func's with yield in them (making them generators)
|
||||
if isinstance(ret, types.GeneratorType):
|
||||
while True:
|
||||
try:
|
||||
inp = inputs.pop() if inputs else None
|
||||
if inp:
|
||||
try:
|
||||
# this mimics a user's reply to a prompt
|
||||
ret.send(inp)
|
||||
except TypeError:
|
||||
next(ret)
|
||||
ret = ret.send(inp)
|
||||
else:
|
||||
# non-input yield, like yield(10). We don't pause
|
||||
# but fire it immediately.
|
||||
next(ret)
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
cmdobj.at_post_cmd()
|
||||
except StopIteration:
|
||||
pass
|
||||
except InterruptCommand:
|
||||
pass
|
||||
|
||||
for inp in inputs:
|
||||
# if there are any inputs left, we may have a non-generator
|
||||
# input to handle (get_input/ask_yes_no that uses a separate
|
||||
# cmdset rather than a yield
|
||||
caller.execute_cmd(inp)
|
||||
|
||||
# At this point the mocked .msg methods on each receiver will have
|
||||
# stored all calls made to them (that's a basic function of the Mock
|
||||
# class). We will not extract them and compare to what we expected to
|
||||
# go to each receiver.
|
||||
|
||||
returned_msgs = {}
|
||||
for receiver, expected_msg in receiver_mapping.items():
|
||||
# get the stored messages from the Mock with Mock.mock_calls.
|
||||
stored_msg = [
|
||||
args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs))
|
||||
for name, args, kwargs in receiver.msg.mock_calls
|
||||
]
|
||||
# we can return this now, we are done using the mock
|
||||
receiver.msg = unmocked_msg_methods[receiver]
|
||||
|
||||
# Get the first element of a tuple if msg received a tuple instead of a string
|
||||
stored_msg = [str(smsg[0])
|
||||
if isinstance(smsg, tuple) else str(smsg) for smsg in stored_msg]
|
||||
if expected_msg is None:
|
||||
# no expected_msg; just build the returned_msgs dict
|
||||
|
||||
returned_msg = "\n".join(str(msg) for msg in stored_msg)
|
||||
returned_msgs[receiver] = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
||||
else:
|
||||
# compare messages to expected
|
||||
|
||||
# set our separator for returned messages based on parsing ansi or not
|
||||
msg_sep = "|" if noansi else "||"
|
||||
|
||||
# We remove Evmenu decorations since that just makes it harder
|
||||
# to write the comparison string. We also strip ansi before this
|
||||
# comparison since otherwise it would mess with the regex.
|
||||
returned_msg = msg_sep.join(
|
||||
_RE_STRIP_EVMENU.sub(
|
||||
"", ansi.parse_ansi(mess, strip_ansi=noansi))
|
||||
for mess in stored_msg).strip()
|
||||
|
||||
# this is the actual test
|
||||
if expected_msg == "" and returned_msg or not returned_msg.startswith(expected_msg):
|
||||
# failed the test
|
||||
raise AssertionError(
|
||||
self._ERROR_FORMAT.format(
|
||||
expected_msg=expected_msg, returned_msg=returned_msg)
|
||||
)
|
||||
# passed!
|
||||
returned_msgs[receiver] = returned_msg
|
||||
|
||||
if len(returned_msgs) == 1:
|
||||
return list(returned_msgs.values())[0]
|
||||
return returned_msgs
|
||||
|
||||
|
||||
@patch("evennia.commands.account.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.admin.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.batchprocess.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.building.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.comms.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.general.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.help.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.syscommands.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.system.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.unloggedin.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
class EvenniaCommandTest(BaseEvenniaTest, CommandTestMixin):
|
||||
"""
|
||||
Commands only using the default settings.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class CommandTest(EvenniaTest, CommandTestMixin):
|
||||
"""
|
||||
Parent class to inherit from - makes tests use your own
|
||||
classes and settings in mygame.
|
||||
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Individual module Tests
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGeneral(EvenniaCommandTest):
|
||||
class TestGeneral(BaseEvenniaCommandTest):
|
||||
def test_look(self):
|
||||
rid = self.room1.id
|
||||
self.call(general.CmdLook(), "here", "Room(#{})\nroom_desc".format(rid))
|
||||
|
|
@ -434,7 +145,7 @@ class TestGeneral(EvenniaCommandTest):
|
|||
self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):")
|
||||
|
||||
|
||||
class TestHelp(EvenniaCommandTest):
|
||||
class TestHelp(BaseEvenniaCommandTest):
|
||||
|
||||
maxDiff = None
|
||||
|
||||
|
|
@ -584,7 +295,7 @@ class TestHelp(EvenniaCommandTest):
|
|||
cmdset=TestCmdSet())
|
||||
|
||||
|
||||
class TestSystem(EvenniaCommandTest):
|
||||
class TestSystem(BaseEvenniaCommandTest):
|
||||
def test_py(self):
|
||||
# we are not testing CmdReload, CmdReset and CmdShutdown, CmdService or CmdTime
|
||||
# since the server is not running during these tests.
|
||||
|
|
@ -608,7 +319,7 @@ _TASK_HANDLER = None
|
|||
def func_test_cmd_tasks():
|
||||
return 'success'
|
||||
|
||||
class TestCmdTasks(EvenniaCommandTest):
|
||||
class TestCmdTasks(BaseEvenniaCommandTest):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
|
@ -768,7 +479,7 @@ class TestCmdTasks(EvenniaCommandTest):
|
|||
self.call(system.CmdTasks(), f'/cancel', wanted_msg)
|
||||
|
||||
|
||||
class TestAdmin(EvenniaCommandTest):
|
||||
class TestAdmin(BaseEvenniaCommandTest):
|
||||
def test_emit(self):
|
||||
self.call(admin.CmdEmit(), "Char2 = Test", "Emitted to Char2:\nTest")
|
||||
|
||||
|
|
@ -799,7 +510,7 @@ class TestAdmin(EvenniaCommandTest):
|
|||
)
|
||||
|
||||
|
||||
class TestAccount(EvenniaCommandTest):
|
||||
class TestAccount(BaseEvenniaCommandTest):
|
||||
def test_ooc_look(self):
|
||||
if settings.MULTISESSION_MODE < 2:
|
||||
self.call(
|
||||
|
|
@ -923,7 +634,7 @@ class TestAccount(EvenniaCommandTest):
|
|||
)
|
||||
|
||||
|
||||
class TestBuilding(EvenniaCommandTest):
|
||||
class TestBuilding(BaseEvenniaCommandTest):
|
||||
def test_create(self):
|
||||
name = settings.BASE_OBJECT_TYPECLASS.rsplit(".", 1)[1]
|
||||
self.call(
|
||||
|
|
@ -1991,7 +1702,7 @@ from evennia.comms.comms import DefaultChannel # noqa
|
|||
|
||||
|
||||
@patch("evennia.commands.default.comms.CHANNEL_DEFAULT_TYPECLASS", DefaultChannel)
|
||||
class TestCommsChannel(EvenniaCommandTest):
|
||||
class TestCommsChannel(BaseEvenniaCommandTest):
|
||||
"""
|
||||
Test the central `channel` command.
|
||||
|
||||
|
|
@ -2214,7 +1925,7 @@ class TestCommsChannel(EvenniaCommandTest):
|
|||
from evennia.commands.default import comms # noqa
|
||||
|
||||
|
||||
class TestComms(EvenniaCommandTest):
|
||||
class TestComms(BaseEvenniaCommandTest):
|
||||
|
||||
def test_page(self):
|
||||
self.call(
|
||||
|
|
@ -2226,7 +1937,7 @@ class TestComms(EvenniaCommandTest):
|
|||
)
|
||||
|
||||
|
||||
class TestBatchProcess(EvenniaCommandTest):
|
||||
class TestBatchProcess(BaseEvenniaCommandTest):
|
||||
"""
|
||||
Test the batch processor.
|
||||
|
||||
|
|
@ -2262,13 +1973,13 @@ class CmdInterrupt(Command):
|
|||
self.msg("in func")
|
||||
|
||||
|
||||
class TestInterruptCommand(EvenniaCommandTest):
|
||||
class TestInterruptCommand(BaseEvenniaCommandTest):
|
||||
def test_interrupt_command(self):
|
||||
ret = self.call(CmdInterrupt(), "")
|
||||
self.assertEqual(ret, "")
|
||||
|
||||
|
||||
class TestUnconnectedCommand(EvenniaCommandTest):
|
||||
class TestUnconnectedCommand(BaseEvenniaCommandTest):
|
||||
def test_info_command(self):
|
||||
# instead of using SERVER_START_TIME (0), we use 86400 because Windows won't let us use anything lower
|
||||
gametime.SERVER_START_TIME = 86400
|
||||
|
|
@ -2288,7 +1999,7 @@ class TestUnconnectedCommand(EvenniaCommandTest):
|
|||
# Test syscommands
|
||||
|
||||
|
||||
class TestSystemCommands(EvenniaCommandTest):
|
||||
class TestSystemCommands(BaseEvenniaCommandTest):
|
||||
def test_simple_defaults(self):
|
||||
self.call(syscommands.SystemNoInput(), "")
|
||||
self.call(syscommands.SystemNoMatch(), "Huh?")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Building menu tests.
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . building_menu import BuildingMenu, CmdNoMatch
|
||||
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ class Submenu(BuildingMenu):
|
|||
self.add_choice("title", key="t", attr="key")
|
||||
|
||||
|
||||
class TestBuildingMenu(EvenniaCommandTest):
|
||||
class TestBuildingMenu(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
super(TestBuildingMenu, self).setUp()
|
||||
self.menu = BuildingMenu(caller=self.char1, obj=self.room1, title="test")
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ Test email login.
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . import email_login
|
||||
|
||||
|
||||
class TestEmailLogin(EvenniaCommandTest):
|
||||
class TestEmailLogin(BaseEvenniaCommandTest):
|
||||
def test_connect(self):
|
||||
self.call(
|
||||
email_login.CmdUnconnectedConnect(),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from textwrap import dedent
|
|||
|
||||
from django.conf import settings
|
||||
from evennia import ScriptDB
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.objects.objects import ExitCommand
|
||||
from evennia.utils import ansi, utils
|
||||
from evennia.utils.create import create_object, create_script
|
||||
|
|
@ -246,7 +246,7 @@ class TestEventHandler(BaseEvenniaTest):
|
|||
self.assertEqual(self.room1.callbacks.all(), {})
|
||||
|
||||
|
||||
class TestCmdCallback(EvenniaCommandTest):
|
||||
class TestCmdCallback(BaseEvenniaCommandTest):
|
||||
|
||||
"""Test the @callback command."""
|
||||
|
||||
|
|
@ -425,7 +425,7 @@ class TestCmdCallback(EvenniaCommandTest):
|
|||
self.assertEqual(callback.valid, True)
|
||||
|
||||
|
||||
class TestDefaultCallbacks(EvenniaCommandTest):
|
||||
class TestDefaultCallbacks(BaseEvenniaCommandTest):
|
||||
|
||||
"""Test the default callbacks."""
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ Test menu_login
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . import menu_login
|
||||
|
||||
|
||||
class TestMenuLogin(EvenniaCommandTest):
|
||||
class TestMenuLogin(BaseEvenniaCommandTest):
|
||||
def test_cmdunloggedlook(self):
|
||||
self.call(menu_login.CmdUnloggedinLook(), "", "======")
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ Legacy Mux comms tests (extracted from 0.9.5)
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . import mux_comms_cmds as comms
|
||||
|
||||
|
||||
class TestLegacyMuxComms(EvenniaCommandTest):
|
||||
class TestLegacyMuxComms(BaseEvenniaCommandTest):
|
||||
"""
|
||||
Test the legacy comms contrib.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Test of the Unixcommand.
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from .unixcommand import UnixCommand
|
||||
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ class CmdDummy(UnixCommand):
|
|||
self.msg("{} * {} = {}".format(nb1, nb2, result))
|
||||
|
||||
|
||||
class TestUnixCommand(EvenniaCommandTest):
|
||||
class TestUnixCommand(BaseEvenniaCommandTest):
|
||||
def test_success(self):
|
||||
"""See the command parsing succeed."""
|
||||
self.call(CmdDummy(), "5 10", "5 * 10 = 50")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Unit tests for the Evscaperoom
|
|||
import inspect
|
||||
import pkgutil
|
||||
from os import path
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia import InterruptCommand
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from evennia.utils import mod_import
|
||||
|
|
@ -15,7 +15,7 @@ from . import objects
|
|||
from . import utils
|
||||
|
||||
|
||||
class TestEvscaperoomCommands(EvenniaCommandTest):
|
||||
class TestEvscaperoomCommands(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.room1 = utils.create_evscaperoom_object("evscaperoom.room.EvscapeRoom", key="Testroom")
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ Test the contrib barter system
|
|||
"""
|
||||
|
||||
from mock import Mock
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from . import barter
|
||||
|
||||
|
||||
class TestBarter(EvenniaCommandTest):
|
||||
class TestBarter(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.tradeitem1 = create_object(key="TradeItem1", location=self.char1)
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ Testing clothing contrib
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from . import clothing
|
||||
|
||||
|
||||
class TestClothingCmd(EvenniaCommandTest):
|
||||
class TestClothingCmd(BaseEvenniaCommandTest):
|
||||
def test_clothingcommands(self):
|
||||
wearer = create_object(clothing.ClothedCharacter, key="Wearer")
|
||||
friend = create_object(clothing.ClothedCharacter, key="Friend")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Unit tests for the crafting system contrib.
|
|||
from unittest import mock
|
||||
from django.test import override_settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.test_resources import BaseEvenniaTestCase
|
||||
from evennia.utils.create import create_object
|
||||
from . import crafting, example_recipes
|
||||
|
|
@ -655,7 +655,7 @@ class TestCraftSword(BaseEvenniaTestCase):
|
|||
@mock.patch("evennia.contrib.game_systems.crafting.crafting._load_recipes", new=mock.MagicMock())
|
||||
@mock.patch("evennia.contrib.game_systems.crafting.crafting._RECIPE_CLASSES", new={"testrecipe": _MockRecipe})
|
||||
@override_settings(CRAFT_RECIPE_MODULES=[])
|
||||
class TestCraftCommand(EvenniaCommandTest):
|
||||
class TestCraftCommand(BaseEvenniaCommandTest):
|
||||
"""Test the crafting command"""
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ Test gendersub contrib.
|
|||
"""
|
||||
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from mock import patch
|
||||
from . import gendersub
|
||||
|
||||
|
||||
class TestGenderSub(EvenniaCommandTest):
|
||||
class TestGenderSub(BaseEvenniaCommandTest):
|
||||
def test_setgender(self):
|
||||
self.call(gendersub.SetGender(), "male", "Your gender was set to male.")
|
||||
self.call(gendersub.SetGender(), "ambiguous", "Your gender was set to ambiguous.")
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ Test mail contrib
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . import mail
|
||||
|
||||
|
||||
class TestMail(EvenniaCommandTest):
|
||||
class TestMail(BaseEvenniaCommandTest):
|
||||
def test_mail(self):
|
||||
self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.account)
|
||||
self.call(mail.CmdMail(), "test", "'test' is not a valid mail id.", caller=self.account)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ Test multidescer contrib.
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . import multidescer
|
||||
|
||||
|
||||
class TestMultidescer(EvenniaCommandTest):
|
||||
class TestMultidescer(BaseEvenniaCommandTest):
|
||||
def test_cmdmultidesc(self):
|
||||
self.call(multidescer.CmdMultiDesc(), "/list", "Stored descs:\ncaller:")
|
||||
self.call(
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import re
|
|||
import itertools
|
||||
from mock import Mock
|
||||
from evennia.utils import search
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from . import puzzles
|
||||
|
||||
|
||||
class TestPuzzles(EvenniaCommandTest):
|
||||
class TestPuzzles(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
super(TestPuzzles, self).setUp()
|
||||
self.steel = create_object(self.object_typeclass, key="steel", location=self.char1.location)
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ Turnbattle tests.
|
|||
"""
|
||||
|
||||
from mock import patch, MagicMock
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
from . import tb_basic, tb_equip, tb_range, tb_items, tb_magic
|
||||
|
||||
|
||||
class TestTurnBattleBasicCmd(EvenniaCommandTest):
|
||||
class TestTurnBattleBasicCmd(BaseEvenniaCommandTest):
|
||||
|
||||
# Test basic combat commands
|
||||
def test_turnbattlecmd(self):
|
||||
|
|
@ -22,7 +22,7 @@ class TestTurnBattleBasicCmd(EvenniaCommandTest):
|
|||
self.call(tb_basic.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleEquipCmd(EvenniaCommandTest):
|
||||
class TestTurnBattleEquipCmd(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
super(TestTurnBattleEquipCmd, self).setUp()
|
||||
self.testweapon = create_object(tb_equip.TBEWeapon, key="test weapon")
|
||||
|
|
@ -45,7 +45,7 @@ class TestTurnBattleEquipCmd(EvenniaCommandTest):
|
|||
self.call(tb_equip.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleRangeCmd(EvenniaCommandTest):
|
||||
class TestTurnBattleRangeCmd(BaseEvenniaCommandTest):
|
||||
# Test range commands
|
||||
def test_turnbattlerangecmd(self):
|
||||
# Start with range module specific commands.
|
||||
|
|
@ -61,7 +61,7 @@ class TestTurnBattleRangeCmd(EvenniaCommandTest):
|
|||
self.call(tb_range.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleItemsCmd(EvenniaCommandTest):
|
||||
class TestTurnBattleItemsCmd(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
super(TestTurnBattleItemsCmd, self).setUp()
|
||||
self.testitem = create_object(key="test item")
|
||||
|
|
@ -78,7 +78,7 @@ class TestTurnBattleItemsCmd(EvenniaCommandTest):
|
|||
self.call(tb_items.CmdRest(), "", "Char rests to recover HP.")
|
||||
|
||||
|
||||
class TestTurnBattleMagicCmd(EvenniaCommandTest):
|
||||
class TestTurnBattleMagicCmd(BaseEvenniaCommandTest):
|
||||
|
||||
# Test magic commands
|
||||
def test_turnbattlemagiccmd(self):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Testing of ExtendedRoom contrib
|
|||
import datetime
|
||||
from mock import patch, Mock
|
||||
from django.conf import settings
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
from . import extended_room
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ class ForceUTCDatetime(datetime.datetime):
|
|||
@patch("evennia.contrib.grid.extended_room.extended_room.datetime.datetime", ForceUTCDatetime)
|
||||
# mock gametime to return April 9, 2064, at 21:06 (spring evening)
|
||||
@patch("evennia.utils.gametime.gametime", new=Mock(return_value=2975000766))
|
||||
class TestExtendedRoom(EvenniaCommandTest):
|
||||
class TestExtendedRoom(BaseEvenniaCommandTest):
|
||||
room_typeclass = extended_room.ExtendedRoom
|
||||
DETAIL_DESC = "A test detail."
|
||||
SPRING_DESC = "A spring description."
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Test map builder.
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . import mapbuilder
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -187,7 +187,7 @@ EXAMPLE2_LEGEND = {
|
|||
}
|
||||
|
||||
|
||||
class TestMapBuilder(EvenniaCommandTest):
|
||||
class TestMapBuilder(BaseEvenniaCommandTest):
|
||||
def test_cmdmapbuilder(self):
|
||||
self.call(
|
||||
mapbuilder.CmdMapBuilder(),
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ Tests of simpledoor.
|
|||
"""
|
||||
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . import simpledoor
|
||||
|
||||
|
||||
class TestSimpleDoor(EvenniaCommandTest):
|
||||
class TestSimpleDoor(BaseEvenniaCommandTest):
|
||||
def test_cmdopen(self):
|
||||
self.call(
|
||||
simpledoor.CmdOpen(),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Slow exit tests.
|
|||
"""
|
||||
|
||||
from mock import Mock, patch
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from . import slow_exit
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ def _cancellable_mockdelay(time, callback, *args, **kwargs):
|
|||
return Mock()
|
||||
|
||||
|
||||
class TestSlowExit(EvenniaCommandTest):
|
||||
class TestSlowExit(BaseEvenniaCommandTest):
|
||||
@patch("evennia.utils.delay", _cancellable_mockdelay)
|
||||
def test_exit(self):
|
||||
exi = create_object(
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ Testing of TestDice.
|
|||
|
||||
"""
|
||||
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from mock import patch
|
||||
from . import dice
|
||||
|
||||
|
||||
@patch("evennia.contrib.rpg.dice.dice.randint", return_value=5)
|
||||
class TestDice(EvenniaCommandTest):
|
||||
class TestDice(BaseEvenniaCommandTest):
|
||||
def test_roll_dice(self, mocked_randint):
|
||||
self.assertEqual(dice.roll_dice(6, 6, modifier=("+", 4)), mocked_randint() * 6 + 4)
|
||||
self.assertEqual(dice.roll_dice(6, 6, conditional=("<", 35)), True)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Tests for RP system
|
|||
"""
|
||||
import time
|
||||
from anything import Anything
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from evennia import create_object
|
||||
|
||||
|
|
@ -278,7 +278,7 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
self.assertEqual(result, (Anything, self.speaker, self.speaker.key))
|
||||
|
||||
|
||||
class TestRPSystemCommands(EvenniaCommandTest):
|
||||
class TestRPSystemCommands(BaseEvenniaCommandTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.char1.swap_typeclass(rpsystem.ContribRPCharacter)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
Tutorial - talking NPC tests.
|
||||
|
||||
"""
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from . import talking_npc
|
||||
|
||||
|
||||
class TestTalkingNPC(EvenniaCommandTest):
|
||||
class TestTalkingNPC(BaseEvenniaCommandTest):
|
||||
def test_talkingnpc(self):
|
||||
npc = create_object(talking_npc.TalkingNPC, key="npctalker", location=self.room1)
|
||||
self.call(talking_npc.CmdTalk(), "", "(You walk up and talk to Char.)")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Test tutorial_world/mob
|
|||
from mock import patch
|
||||
from twisted.trial.unittest import TestCase as TwistedTestCase
|
||||
from twisted.internet.base import DelayedCall
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from evennia.utils.create import create_object
|
||||
from evennia.utils.test_resources import BaseEvenniaTest, mockdelay, mockdeferLater
|
||||
from . import mob, objects as tutobjects, rooms as tutrooms
|
||||
|
|
@ -30,7 +30,7 @@ class TestTutorialWorldMob(BaseEvenniaTest):
|
|||
DelayedCall.debug = True
|
||||
|
||||
|
||||
class TestTutorialWorldObjects(TwistedTestCase, EvenniaCommandTest):
|
||||
class TestTutorialWorldObjects(TwistedTestCase, BaseEvenniaCommandTest):
|
||||
def test_tutorialobj(self):
|
||||
obj1 = create_object(tutobjects.TutorialObject, key="tutobj")
|
||||
obj1.reset()
|
||||
|
|
@ -129,7 +129,7 @@ class TestTutorialWorldObjects(TwistedTestCase, EvenniaCommandTest):
|
|||
self.call(tutobjects.CmdGetWeapon(), "", "You find Rusty sword.", obj=rack)
|
||||
|
||||
|
||||
class TestTutorialWorldRooms(EvenniaCommandTest):
|
||||
class TestTutorialWorldRooms(BaseEvenniaCommandTest):
|
||||
def test_cmdtutorial(self):
|
||||
room = create_object(tutrooms.TutorialRoom, key="tutroom")
|
||||
self.char1.location = room
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ Test the main server component
|
|||
"""
|
||||
|
||||
from unittest import TestCase
|
||||
from mock import MagicMock, patch, DEFAULT, call
|
||||
from django.test import override_settings
|
||||
from evennia.utils.test_resources import unload_module
|
||||
from mock import MagicMock, patch, DEFAULT, call
|
||||
|
||||
|
||||
@patch("evennia.server.server.LoopingCall", new=MagicMock())
|
||||
|
|
@ -191,7 +190,7 @@ class TestServer(TestCase):
|
|||
evennia.run_initial_setup()
|
||||
acct.delete()
|
||||
|
||||
@override_settings(DEFAULT_HOME="#1")
|
||||
@override_settings(_TEST_ENVIRONMENT=True)
|
||||
def test_run_init_hooks(self):
|
||||
from evennia.utils import create
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,34 @@
|
|||
"""
|
||||
Various helper resources for writing unittests.
|
||||
|
||||
Classes for testing Evennia core:
|
||||
|
||||
- `BaseEvenniaTestCase` - no default objects, only enforced default settings
|
||||
- `BaseEvenniaTest` - all default objects, enforced default settings
|
||||
- `BaseEvenniaCommandTest` - for testing Commands, enforced default settings
|
||||
|
||||
Classes for testing game folder content:
|
||||
|
||||
- `EvenniaTestCase` - no default objects, using gamedir settings (identical to
|
||||
standard Python TestCase)
|
||||
- `EvenniaTest` - all default objects, using gamedir settings
|
||||
- `EvenniaCommandTest` - for testing game folder commands, using gamedir settings
|
||||
|
||||
Other:
|
||||
|
||||
- `EvenniaTestMixin` - A class mixin for creating the test environment objects, for
|
||||
making custom tests.
|
||||
- `EvenniaCommandMixin` - A class mixin that adds support for command testing with the .call()
|
||||
helper. Used by the command-test classes, but can be used for making a customt test class.
|
||||
|
||||
"""
|
||||
import sys
|
||||
import re
|
||||
import types
|
||||
from twisted.internet.defer import Deferred
|
||||
from django.conf import settings
|
||||
from django.test import TestCase, override_settings
|
||||
from mock import Mock, patch
|
||||
from mock import Mock, patch, MagicMock
|
||||
from evennia.objects.objects import DefaultObject, DefaultCharacter, DefaultRoom, DefaultExit
|
||||
from evennia.accounts.accounts import DefaultAccount
|
||||
from evennia.scripts.scripts import DefaultScript
|
||||
|
|
@ -14,8 +36,14 @@ from evennia.server.serversession import ServerSession
|
|||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia.utils import create
|
||||
from evennia.utils.idmapper.models import flush_cache
|
||||
from evennia.utils.utils import all_from_module
|
||||
from evennia.utils.utils import all_from_module, to_str
|
||||
from evennia.utils import ansi
|
||||
from evennia import settings_default
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.commands.command import InterruptCommand
|
||||
|
||||
|
||||
_RE_STRIP_EVMENU = re.compile(r"^\+|-+\+|\+-+|--+|\|(?:\s|$)", re.MULTILINE)
|
||||
|
||||
|
||||
# set up a 'pristine' setting, unaffected by any changes in mygame
|
||||
|
|
@ -242,14 +270,275 @@ class EvenniaTestMixin:
|
|||
super().tearDown()
|
||||
|
||||
|
||||
@patch("evennia.server.portal.portal.LoopingCall", new=MagicMock())
|
||||
class EvenniaCommandTestMixin:
|
||||
"""
|
||||
Mixin to add to a test in order to provide the `.call` helper for
|
||||
testing the execution and returns of a command.
|
||||
|
||||
Tests a Command by running it and comparing what messages it sends with
|
||||
expected values. This tests without actually spinning up the cmdhandler
|
||||
for every test, which is more controlled.
|
||||
|
||||
Example:
|
||||
::
|
||||
|
||||
from commands.echo import CmdEcho
|
||||
|
||||
class MyCommandTest(EvenniaTest, CommandTestMixin):
|
||||
|
||||
def test_echo(self):
|
||||
'''
|
||||
Test that the echo command really returns
|
||||
what you pass into it.
|
||||
'''
|
||||
self.call(MyCommand(), "hello world!",
|
||||
"You hear your echo: 'Hello world!'")
|
||||
|
||||
"""
|
||||
|
||||
# formatting for .call's error message
|
||||
_ERROR_FORMAT = """
|
||||
=========================== Wanted message ===================================
|
||||
{expected_msg}
|
||||
=========================== Returned message =================================
|
||||
{returned_msg}
|
||||
==============================================================================
|
||||
""".rstrip()
|
||||
|
||||
def call(
|
||||
self,
|
||||
cmdobj,
|
||||
input_args,
|
||||
msg=None,
|
||||
cmdset=None,
|
||||
noansi=True,
|
||||
caller=None,
|
||||
receiver=None,
|
||||
cmdstring=None,
|
||||
obj=None,
|
||||
inputs=None,
|
||||
raw_string=None,
|
||||
):
|
||||
"""
|
||||
Test a command by assigning all the needed properties to a cmdobj and
|
||||
running the sequence. The resulting `.msg` calls will be mocked and
|
||||
the text= calls to them compared to a expected output.
|
||||
|
||||
Args:
|
||||
cmdobj (Command): The command object to use.
|
||||
input_args (str): This should be the full input the Command should
|
||||
see, such as 'look here'. This will become `.args` for the Command
|
||||
instance to parse.
|
||||
msg (str or dict, optional): This is the expected return value(s)
|
||||
returned through `caller.msg(text=...)` calls in the command. If a string, the
|
||||
receiver is controlled with the `receiver` kwarg (defaults to `caller`).
|
||||
If this is a `dict`, it is a mapping
|
||||
`{receiver1: "expected1", receiver2: "expected2",...}` and `receiver` is
|
||||
ignored. The message(s) are compared with the actual messages returned
|
||||
to the receiver(s) as the Command runs. Each check uses `.startswith`,
|
||||
so you can choose to only include the first part of the
|
||||
returned message if that's enough to verify a correct result. EvMenu
|
||||
decorations (like borders) are stripped and should not be included. This
|
||||
should also not include color tags unless `noansi=False`.
|
||||
If the command returns texts in multiple separate `.msg`-
|
||||
calls to a receiver, separate these with `|` if `noansi=True`
|
||||
(default) and `||` if `noansi=False`. If no `msg` is given (`None`),
|
||||
then no automatic comparison will be done.
|
||||
cmdset (str, optional): If given, make `.cmdset` available on the Command
|
||||
instance as it runs. While `.cmdset` is normally available on the
|
||||
Command instance by default, this is usually only used by
|
||||
commands that explicitly operates/displays cmdsets, like
|
||||
`examine`.
|
||||
noansi (str, optional): By default the color tags of the `msg` is
|
||||
ignored, this makes them significant. If unset, `msg` must contain
|
||||
the same color tags as the actual return message.
|
||||
caller (Object or Account, optional): By default `self.char1` is used as the
|
||||
command-caller (the `.caller` property on the Command). This allows to
|
||||
execute with another caller, most commonly an Account.
|
||||
receiver (Object or Account, optional): This is the object to receive the
|
||||
return messages we want to test. By default this is the same as `caller`
|
||||
(which in turn defaults to is `self.char1`). Note that if `msg` is
|
||||
a `dict`, this is ignored since the receiver is already specified there.
|
||||
cmdstring (str, optional): Normally this is the Command's `key`.
|
||||
This allows for tweaking the `.cmdname` property of the
|
||||
Command`. This isb used for commands with multiple aliases,
|
||||
where the command explicitly checs which alias was used to
|
||||
determine its functionality.
|
||||
obj (str, optional): This sets the `.obj` property of the Command - the
|
||||
object on which the Command 'sits'. By default this is the same as `caller`.
|
||||
This can be used for testing on-object Command interactions.
|
||||
inputs (list, optional): A list of strings to pass to functions that pause to
|
||||
take input from the user (normally using `@interactive` and
|
||||
`ret = yield(question)` or `evmenu.get_input`). Each element of the
|
||||
list will be passed into the command as if the user wrote that at the prompt.
|
||||
raw_string (str, optional): Normally the `.raw_string` property is set as
|
||||
a combination of your `key/cmdname` and `input_args`. This allows
|
||||
direct control of what this is, for example for testing edge cases
|
||||
or malformed inputs.
|
||||
|
||||
Returns:
|
||||
str or dict: The message sent to `receiver`, or a dict of
|
||||
`{receiver: "msg", ...}` if multiple are given. This is usually
|
||||
only used with `msg=None` to do the validation externally.
|
||||
|
||||
Raises:
|
||||
AssertionError: If the returns of `.msg` calls (tested with `.startswith`) does not
|
||||
match `expected_input`.
|
||||
|
||||
Notes:
|
||||
As part of the tests, all methods of the Command will be called in
|
||||
the proper order:
|
||||
|
||||
- cmdobj.at_pre_cmd()
|
||||
- cmdobj.parse()
|
||||
- cmdobj.func()
|
||||
- cmdobj.at_post_cmd()
|
||||
|
||||
"""
|
||||
# The `self.char1` is created in the `EvenniaTest` base along with
|
||||
# other helper objects like self.room and self.obj
|
||||
caller = caller if caller else self.char1
|
||||
cmdobj.caller = caller
|
||||
cmdobj.cmdname = cmdstring if cmdstring else cmdobj.key
|
||||
cmdobj.raw_cmdname = cmdobj.cmdname
|
||||
cmdobj.cmdstring = cmdobj.cmdname # deprecated
|
||||
cmdobj.args = input_args
|
||||
cmdobj.cmdset = cmdset
|
||||
cmdobj.session = SESSIONS.session_from_sessid(1)
|
||||
cmdobj.account = self.account
|
||||
cmdobj.raw_string = raw_string if raw_string is not None else cmdobj.key + " " + input_args
|
||||
cmdobj.obj = obj or (caller if caller else self.char1)
|
||||
inputs = inputs or []
|
||||
|
||||
# set up receivers
|
||||
receiver_mapping = {}
|
||||
if isinstance(msg, dict):
|
||||
# a mapping {receiver: msg, ...}
|
||||
receiver_mapping = {recv: str(msg).strip() if msg else None
|
||||
for recv, msg in msg.items()}
|
||||
else:
|
||||
# a single expected string and thus a single receiver (defaults to caller)
|
||||
receiver = receiver if receiver else caller
|
||||
receiver_mapping[receiver] = str(msg).strip() if msg is not None else None
|
||||
|
||||
unmocked_msg_methods = {}
|
||||
for receiver in receiver_mapping:
|
||||
# save the old .msg method so we can get it back
|
||||
# cleanly after the test
|
||||
unmocked_msg_methods[receiver] = receiver.msg
|
||||
# replace normal `.msg` with a mock
|
||||
receiver.msg = Mock()
|
||||
|
||||
# Run the methods of the Command. This mimics what happens in the
|
||||
# cmdhandler. This will have the mocked .msg be called as part of the
|
||||
# execution. Mocks remembers what was sent to them so we will be able
|
||||
# to retrieve what was sent later.
|
||||
try:
|
||||
if cmdobj.at_pre_cmd():
|
||||
return
|
||||
cmdobj.parse()
|
||||
ret = cmdobj.func()
|
||||
|
||||
# handle func's with yield in them (making them generators)
|
||||
if isinstance(ret, types.GeneratorType):
|
||||
while True:
|
||||
try:
|
||||
inp = inputs.pop() if inputs else None
|
||||
if inp:
|
||||
try:
|
||||
# this mimics a user's reply to a prompt
|
||||
ret.send(inp)
|
||||
except TypeError:
|
||||
next(ret)
|
||||
ret = ret.send(inp)
|
||||
else:
|
||||
# non-input yield, like yield(10). We don't pause
|
||||
# but fire it immediately.
|
||||
next(ret)
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
cmdobj.at_post_cmd()
|
||||
except StopIteration:
|
||||
pass
|
||||
except InterruptCommand:
|
||||
pass
|
||||
|
||||
for inp in inputs:
|
||||
# if there are any inputs left, we may have a non-generator
|
||||
# input to handle (get_input/ask_yes_no that uses a separate
|
||||
# cmdset rather than a yield
|
||||
caller.execute_cmd(inp)
|
||||
|
||||
# At this point the mocked .msg methods on each receiver will have
|
||||
# stored all calls made to them (that's a basic function of the Mock
|
||||
# class). We will not extract them and compare to what we expected to
|
||||
# go to each receiver.
|
||||
|
||||
returned_msgs = {}
|
||||
for receiver, expected_msg in receiver_mapping.items():
|
||||
# get the stored messages from the Mock with Mock.mock_calls.
|
||||
stored_msg = [
|
||||
args[0] if args and args[0] else kwargs.get("text", to_str(kwargs))
|
||||
for name, args, kwargs in receiver.msg.mock_calls
|
||||
]
|
||||
# we can return this now, we are done using the mock
|
||||
receiver.msg = unmocked_msg_methods[receiver]
|
||||
|
||||
# Get the first element of a tuple if msg received a tuple instead of a string
|
||||
stored_msg = [str(smsg[0])
|
||||
if isinstance(smsg, tuple) else str(smsg) for smsg in stored_msg]
|
||||
if expected_msg is None:
|
||||
# no expected_msg; just build the returned_msgs dict
|
||||
|
||||
returned_msg = "\n".join(str(msg) for msg in stored_msg)
|
||||
returned_msgs[receiver] = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
||||
else:
|
||||
# compare messages to expected
|
||||
|
||||
# set our separator for returned messages based on parsing ansi or not
|
||||
msg_sep = "|" if noansi else "||"
|
||||
|
||||
# We remove Evmenu decorations since that just makes it harder
|
||||
# to write the comparison string. We also strip ansi before this
|
||||
# comparison since otherwise it would mess with the regex.
|
||||
returned_msg = msg_sep.join(
|
||||
_RE_STRIP_EVMENU.sub(
|
||||
"", ansi.parse_ansi(mess, strip_ansi=noansi))
|
||||
for mess in stored_msg).strip()
|
||||
|
||||
# this is the actual test
|
||||
if expected_msg == "" and returned_msg or not returned_msg.startswith(expected_msg):
|
||||
# failed the test
|
||||
raise AssertionError(
|
||||
self._ERROR_FORMAT.format(
|
||||
expected_msg=expected_msg, returned_msg=returned_msg)
|
||||
)
|
||||
# passed!
|
||||
returned_msgs[receiver] = returned_msg
|
||||
|
||||
if len(returned_msgs) == 1:
|
||||
return list(returned_msgs.values())[0]
|
||||
return returned_msgs
|
||||
|
||||
|
||||
# Base testing classes
|
||||
|
||||
@override_settings(**DEFAULT_SETTINGS)
|
||||
class BaseEvenniaTestCase(TestCase):
|
||||
"""
|
||||
Base test (with no default objects) but with
|
||||
enforced default settings.
|
||||
Base test (with no default objects) but with enforced default settings.
|
||||
|
||||
"""
|
||||
|
||||
class EvenniaTestCase(TestCase):
|
||||
"""
|
||||
For use with gamedir settings; Just like the normal test case, only for naming consistency.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@override_settings(**DEFAULT_SETTINGS)
|
||||
class BaseEvenniaTest(EvenniaTestMixin, TestCase):
|
||||
|
|
@ -258,7 +547,6 @@ class BaseEvenniaTest(EvenniaTestMixin, TestCase):
|
|||
|
||||
"""
|
||||
|
||||
|
||||
class EvenniaTest(EvenniaTestMixin, TestCase):
|
||||
"""
|
||||
This test class is intended for inheriting in mygame tests.
|
||||
|
|
@ -273,3 +561,28 @@ class EvenniaTest(EvenniaTestMixin, TestCase):
|
|||
exit_typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
||||
script_typeclass = settings.BASE_SCRIPT_TYPECLASS
|
||||
|
||||
|
||||
@patch("evennia.commands.account.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.admin.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.batchprocess.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.building.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.comms.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.general.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.help.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.syscommands.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.system.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
@patch("evennia.commands.unloggedin.COMMAND_DEFAULT_CLASS", MuxCommand)
|
||||
class BaseEvenniaCommandTest(BaseEvenniaTest, EvenniaCommandTestMixin):
|
||||
"""
|
||||
Commands only using the default settings.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class EvenniaCommandTest(EvenniaTest, EvenniaCommandTestMixin):
|
||||
"""
|
||||
Parent class to inherit from - makes tests use your own
|
||||
classes and settings in mygame.
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ Test eveditor
|
|||
"""
|
||||
|
||||
from evennia.utils import eveditor
|
||||
from evennia.commands.default.tests import EvenniaCommandTest
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
|
||||
|
||||
class TestEvEditor(EvenniaCommandTest):
|
||||
class TestEvEditor(BaseEvenniaCommandTest):
|
||||
def test_eveditor_view_cmd(self):
|
||||
eveditor.EvEditor(self.char1)
|
||||
self.call(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue