Difference between revisions of "PyOGP Package Unittests"
Enus Linden (talk | contribs) |
Enus Linden (talk | contribs) |
||
Line 6: | Line 6: | ||
pyogp.lib.base | pyogp.lib.base | ||
pyogp.lib.client | pyogp.lib.client | ||
Unit tests live in directories named test/ within any directory in any package. | |||
== Python test types == | == Python test types == | ||
Line 164: | Line 166: | ||
OK | OK | ||
</pre> | </pre> | ||
== Writing Test Cases == | |||
Tests can be written using standard unittest. The tests in pyogp.interop cover some ogp and a couple of legacy cases, these need to be updated to work. | |||
Testing only the call to login.cgi is unique, we don't need to spawn the client in a coroutine, nor do we need to keep the client alive, we just need to post to the login endpoint and evaluate the response. | |||
=== pyogp.interop.tests.test_legacy_login === | |||
<python>import unittest, doctest | |||
import ConfigParser | |||
from pkg_resources import resource_stream | |||
import time | |||
import uuid | |||
import pprint | |||
from pyogp.lib.base.agent import Agent | |||
from pyogp.lib.base.datatypes import UUID | |||
from pyogp.lib.base.exc import LoginError | |||
from pyogp.lib.base.settings import Settings | |||
import helpers | |||
class AuthLegacyLoginTest(unittest.TestCase): | |||
def setUp(self): | |||
# initialize the config | |||
self.config = ConfigParser.ConfigParser() | |||
self.config.readfp(resource_stream(__name__, 'testconfig.cfg')) | |||
self.test_setup_config_name = 'test_interop_account' | |||
self.firstname = self.config.get(self.test_setup_config_name, 'firstname') | |||
self.lastname = self.config.get(self.test_setup_config_name, 'lastname') | |||
self.password = self.config.get(self.test_setup_config_name, 'password') | |||
self.agent_id = self.config.get(self.test_setup_config_name, 'agent_id') | |||
self.login_uri = self.config.get(self.test_setup_config_name, 'login_uri') | |||
self.region = self.config.get('test_interop_regions', 'start_region_uri') | |||
self.successful_login_reponse_params = ['last_name', 'sim_ip', 'inventory-lib-root', | |||
'start_location', 'inventory-lib-owner', 'udp_blacklist', 'home', 'message', | |||
'agent_access_max', 'first_name', 'agent_region_access', 'circuit_code', 'sim_port', | |||
'seconds_since_epoch', 'secure_session_id', 'look_at', 'ao_transition', 'agent_id', | |||
'inventory_host', 'region_y', 'region_x', 'seed_capability', 'agent_access', 'session_id', | |||
'login'] | |||
self.settings = Settings() | |||
self.settings.MULTIPLE_SIM_CONNECTIONS = False | |||
self.client = Agent(self.settings, self.firstname, self.lastname, self.password) | |||
def tearDown(self): | |||
if self.client.connected: | |||
self.client.logout() | |||
def test_base_login(self): | |||
""" login with an account which should just work """ | |||
self.client.settings.ENABLE_INVENTORY_MANAGEMENT = False | |||
self.client.login(loginuri = self.login_uri, start_location = self.region, connect_region = False) | |||
# Make sure that the login response attributes propagate properly, | |||
# Make sure the login against a grid has worked | |||
self.assertEquals(self.client.grid_type, | |||
'Legacy', | |||
'Storing the wrong grid type based on a \'legacy\' login request') | |||
self.assertEquals(self.client.firstname, self.firstname) | |||
self.assertEquals(self.client.lastname, self.lastname) | |||
self.assertEquals(self.client.lastname, self.lastname) | |||
self.assertEquals(self.client.name, self.firstname + ' ' + self.lastname) | |||
self.assertEquals(self.client.connected, True) | |||
self.assertNotEquals(self.client.agent_id, None) | |||
self.assertEquals(str(self.client.agent_id), self.agent_id) | |||
self.assertNotEquals(self.client.session_id, None) | |||
self.assertNotEquals(self.client.secure_session_id, None) | |||
self.assertEquals(self.client.login_response['last_name'], self.lastname) | |||
self.assertEquals(self.client.login_response['first_name'], '"' + self.firstname + '"') | |||
self.assertEquals(self.client.login_response['login'], 'true') | |||
self.assertEquals(self.client.login_response['secure_session_id'], str(self.client.secure_session_id)) | |||
self.assertEquals(self.client.login_response['session_id'], str(self.client.session_id)) | |||
self.assertEquals(self.client.login_response['agent_id'], str(self.client.agent_id)) | |||
self.assertNotEquals(self.client.login_response['seed_capability'], '') | |||
fail = 0 | |||
fail_extra = 0 | |||
fail_missing = 0 | |||
extra_keys = '' | |||
missing_keys = '' | |||
for key in self.client.login_response: | |||
try: | |||
self.successful_login_reponse_params.index(key) # if the key is in our valid list, sweet | |||
except: | |||
fail_extra = 1 | |||
extra_keys = extra_keys + ' ' + key | |||
for key in self.client.login_response: | |||
try: | |||
self.successful_login_reponse_params.index(key) # if the key is in our valid list, sweet | |||
except: | |||
fail_missing = 1 | |||
missing_keys = missing_keys + ' ' + key | |||
self.assertEquals(fail_extra, 0, 'login response has additional keys: ' + extra_keys) | |||
self.assertEquals(fail_missing, 0, 'login response is missing keys: ' + missing_keys) | |||
def test_login_with_bad_password(self): | |||
self.client.settings.ENABLE_INVENTORY_MANAGEMENT = False | |||
self.assertRaises(LoginError, | |||
self.client.login, | |||
loginuri = self.login_uri, | |||
password = 'BadPassword', | |||
start_location = self.region, | |||
connect_region = False) | |||
def test_suite(): | |||
from unittest import TestSuite, makeSuite | |||
suite = TestSuite() | |||
suite.addTest(makeSuite(AuthLegacyLoginTest)) | |||
return suite</python> | |||
[[Category: Pyogp_Client_Lib]] | [[Category: Pyogp_Client_Lib]] | ||
[[Category: Pyogp]] | [[Category: Pyogp]] |
Revision as of 00:16, 19 October 2009
Overview
Each package in the lib should have unit tests, which cover as much code as possible. The unit tests evaluate the integrity of the library code itself, in terms of apis, code paths, and dependencies.
Packages under unit test right now:
pyogp.lib.base pyogp.lib.client
Unit tests live in directories named test/ within any directory in any package.
Python test types
unittest
Unit tests are written as standard python unittest implementations. For more on this, see http://docs.python.org/library/unittest.html.
doctest
doctests are narrative sample uses of code, that may be run via various test wrappers. For more, see http://docs.python.org/library/doctest.html.
Running tests
nose
We use Nose! http://somethingaboutorange.com/mrl/projects/nose/0.11.1/
Buildout grabs the necessary module. If you are an enterprising soul, please download the nose source and install to your native python or virtualenv instance.
Nose Install Steps: # http://somethingaboutorange.com/mrl/projects/nose/nose-0.11.1.tar.gz # gzip -dc nose-0.11.1.tar.gz | tar xf - # cd nose-0.11.1 # python setup.py install
in buildout
After running buildout, there will be a bin/unittests script available. Simply run it, passing which package's unit tests to run...
bin/unittests --where=src/pyogp.lib.client/ bin/unittests --where=src/pyogp.lib.base/
per packages test runs
There is a test.py in each of pyogp.lib.*.tests. It works in a buildout context by adding to path all the necessary packages and dependencies, or works outside of buildout assuming your python path has all the necessary modules sourced.
To run it, simply:
cd pyogp/lib/base/tests python test.py
Pass test.py a file name, or to run all tests in the package, pass '..'.
Ensure the necessary modules are in your python instance's path:
Here's what the buildout generated nose test wrapper looks like on Enus' machine:
#!/Users/enus/svn/buildout/bin/python import sys sys.path[0:0] = [ '/Users/enus/svn/buildout/eggs/uuid-1.30-py2.5.egg', '/Users/enus/svn/buildout/eggs/indra.base-1.0-py2.5.egg', '/Users/enus/svn/buildout/eggs/eventlet-0.8.14-py2.5.egg', '/Users/enus/svn/buildout/eggs/wsgiref-0.1.2-py2.5.egg', '/Users/enus/svn/buildout/src/pyogp.lib.base', '/Users/enus/svn/buildout/eggs/nose-0.11.1-py2.5.egg', '/Users/enus/svn/buildout/eggs/WebOb-0.9.6.1-py2.5.egg', '/Users/enus/svn/buildout/eggs/elementtree-1.2.7_20070827_preview-py2.5.egg', '/Users/enus/svn/buildout/eggs/setuptools-0.6c9-py2.5.egg', '/Users/enus/svn/buildout/eggs/pyOpenSSL-0.9-py2.5-macosx-10.5-i386.egg', '/Users/enus/svn/buildout/eggs/greenlet-0.2-py2.5-macosx-10.5-i386.egg', '/Users/enus/svn/buildout/eggs/indra.util-1.0-py2.5.egg', ] import nose if __name__ == '__main__': nose.main(argv=['nose']+sys.argv[1:])
Evaluating test coverage
Nose has a great tie into the coverage module (http://nedbatchelder.com/code/modules/rees-coverage.html), which evaluates code coverage by our tests. Buildout automatically installs the necessary module, or, you can install it to your python instance via "easy_install coverage".
To run the unittests via a buildout instance, add the following flags (making sure package names match up):
--with-coverage --cover-package=pyogp.lib.client
For more on the nose test wrapper, see bin/unittests -h.
Code Coverage
Currently, coverage of code by unittest is OK, but, we need to improve it!
pyogp.lib.client
Current coverage:
[11:32:17] [enus@sune] buildout$ bin/unittests --where=src/pyogp.lib.client/ --with-coverage --cover-package=pyogp.lib.client Name Stmts Exec Cover Missing ------------------------------------------------------------- pyogp.lib.client 1 1 100% pyogp.lib.client.agent 513 164 31% 35-49, 71, 82, 179-180, 213, 216, 222-239, 272, 281-303, 311-331, 336-345, 350-359, 365-431, 437-446, 452-457, 477-486, 496-533, 552-570, 575-580, 586-587, 593, 600-615, 620-632, 637-644, 649, 658-674, 679-700, 705-746, 751-757, 762-787, 792-804, 818-872, 881-925, 930-953, 967-980, 986-1017, 1024-1044, 1052-1068, 1073, 1080-1086, 1090-1093, 1098-1101, 1105, 1109-1112, 1117-1119 pyogp.lib.client.appearance 151 76 50% 76-83, 89-97, 122-135, 151-162, 165, 171, 179-183, 194-199, 216-226, 234-249, 257-280, 294-310, 335, 341-347, 354-355 pyogp.lib.client.assets 125 30 24% 58, 68-123, 145-168, 174, 177, 185-192, 202-211, 215-217, 222-231, 234-261 pyogp.lib.client.event_queue 153 90 58% 31, 39, 64, 73, 104, 107-109, 118-125, 136-149, 173, 179, 184-215, 220-235, 248-273 pyogp.lib.client.event_system 89 71 79% 43, 60-67, 82-84, 173-174, 184-185, 193-200, 205, 210 pyogp.lib.client.exc 136 78 57% 36, 42, 45, 57-60, 64, 73, 76, 86-90, 94, 116, 119, 128, 131, 141-142, 145, 160-161, 164, 173-174, 177, 186-187, 190, 199-200, 203, 212-213, 216, 225-226, 229, 238-239, 242, 261, 273, 285, 302, 305, 317, 330, 333, 345, 348, 356, 359, 367, 370 pyogp.lib.client.groups 243 54 22% 65-79, 85-90, 97-111, 116-130, 135-144, 149-156, 174-205, 210-215, 221-224, 229-236, 241, 246-252, 262-273, 278-287, 292-310, 315, 320, 325-329, 333-339, 347-359, 364, 369-375, 380-382, 387-404, 424-451, 469-477, 484-488, 492, 496-520, 524-527 pyogp.lib.client.inventory 495 181 36% 73-80, 84, 88, 93-152, 157-164, 173-180, 191-193, 199-202, 227, 243, 275-277, 295-302, 312, 320-322, 332-338, 350, 362, 373, 378-384, 389-391, 399-408, 417-431, 436-468, 473-480, 522, 559-560, 567-572, 577, 582, 587, 592, 599-601, 606-630, 635-659, 664-697, 702-705, 710-742, 749, 754, 759, 764-775, 780, 784-812, 816-867, 897, 902-909, 914, 924-933, 938, 948-956, 1021-1027, 1036-1040, 1046-1063, 1075-1104, 1109-1150 pyogp.lib.client.login 205 176 85% 111, 129, 150, 171, 201, 210, 225-226, 234, 281-283, 293, 300-301, 308-313, 341, 343, 347, 361, 370, 374-377, 389, 406, 433 pyogp.lib.client.objects 612 276 45% 114, 120-132, 143, 159-190, 200, 207, 212-220, 225-227, 235-239, 244-257, 262-284, 289-294, 298-303, 329-332, 340, 354, 357, 367-376, 382-391, 425-461, 466-633, 639-663, 691-693, 729-859, 907-908, 919-967, 971-973, 977-1014, 1037-1067, 1173, 1183-1195, 1203-1205, 1213-1215, 1223-1225, 1233-1235, 1242-1244, 1251-1253, 1260, 1268-1276, 1283, 1291-1299, 1304, 1309-1323, 1328-1344, 1351, 1358-1365, 1372, 1379-1386 pyogp.lib.client.parcel 372 69 18% 99-112, 122-127, 132-185, 190-212, 219-234, 239-279, 284-299, 304-307, 311, 315, 319, 325-328, 333-338, 343-359, 364-376, 381-389, 394-412, 417-424, 429-447, 452-456, 462, 467-482, 489, 521, 554, 587, 615, 642, 670, 696-699, 704-713, 721-723, 747-750, 755-763, 768-787, 797-873, 878-882, 889, 921, 945, 978, 1006, 1030, 1051, 1083, 1107, 1139 pyogp.lib.client.permissions 23 18 78% 57-61 pyogp.lib.client.region 259 91 35% 34, 40-41, 43, 78, 85, 96, 189-195, 199, 223-229, 237-240, 245-250, 255-272, 277-285, 289-315, 321-331, 336-351, 356-361, 366-367, 373-385, 390-396, 401-407, 412-416, 433-448, 453-460, 465-472, 477-514, 519-523, 528-529, 535-565, 570, 575, 580-586, 597-620, 623 pyogp.lib.client.settings 67 55 82% 146-155, 178-179 pyogp.lib.client.visualparams 502 495 98% 42-44, 50-53 ------------------------------------------------------------- TOTAL 3946 1925 48% ---------------------------------------------------------------------- Ran 94 tests in 14.455s FAILED (errors=1)
pyogp.lib.base
Nice coverage!
[11:32:40] [enus@sune] buildout$ bin/unittests --where=src/pyogp.lib.base/ --with-coverage --cover-package=pyogp.lib.base Name Stmts Exec Cover Missing ---------------------------------------------------------------------- pyogp.lib.base 1 1 100% pyogp.lib.base.datatypes 106 73 68% 45, 49, 53, 72, 75, 80, 88-91, 107, 111, 115, 119, 136-141, 150, 153, 158, 184-189, 208, 213, 222-225, 230-232 pyogp.lib.base.exc 136 77 56% 33, 36, 42, 45, 57-60, 64, 73, 76, 86-90, 94, 116, 119, 128, 131, 141-142, 145, 160-161, 164, 173-174, 177, 186-187, 190, 199-200, 203, 212-213, 216, 225-226, 229, 238-239, 242, 261, 273, 285, 302, 305, 317, 330, 333, 345, 348, 356, 359, 367, 370 pyogp.lib.base.message 1 1 100% pyogp.lib.base.message.circuit 84 69 82% 39, 78-83, 91, 98-104, 134, 149-150, 154 pyogp.lib.base.message.data 4 4 100% pyogp.lib.base.message.data_packer 63 48 76% 66, 69-70, 73-75, 78, 81, 84-87, 92, 99, 101 pyogp.lib.base.message.data_unpacker 65 57 87% 76-80, 83-84, 91, 94, 100 pyogp.lib.base.message.llsd_builder 39 38 97% 34 pyogp.lib.base.message.message 63 42 66% 35-36, 43-44, 109-110, 114, 119-136, 141 pyogp.lib.base.message.message_handler 44 29 65% 38, 47-49, 60, 75-78, 88-90, 93, 97, 100-102, 106 pyogp.lib.base.message.msgtypes 83 76 91% 45-47, 118, 125, 127, 143 pyogp.lib.base.message.template 133 105 78% 46, 63, 71, 84, 87-92, 98, 101, 105, 116, 119, 121, 145, 148, 151, 154, 187, 190, 196, 199, 202, 205, 208, 211, 214 pyogp.lib.base.message.template_dict 58 52 89% 49-52, 100, 106 pyogp.lib.base.message.template_parser 228 187 82% 172-173, 177, 223, 252, 266-278, 281-294, 297-305, 309-317, 320 pyogp.lib.base.message.udpdeserializer 180 139 77% 52, 76-78, 116-125, 132, 138, 145-147, 170, 175, 184, 193, 197, 220-230, 240, 262, 274-275, 296-297, 308-315, 321-323, 335 pyogp.lib.base.message.udpdispatcher 149 108 72% 80, 111, 124-130, 135, 141, 147, 164, 173, 178, 190, 192, 203-211, 220-224, 242-255, 281, 287-288, 297-301, 305 pyogp.lib.base.message.udpserializer 66 57 86% 72, 89, 110-111, 131, 138, 143-147 pyogp.lib.base.network 1 1 100% pyogp.lib.base.network.net 27 14 51% 31, 35-38, 41-50, 66 pyogp.lib.base.settings 67 45 67% 146-155, 167-198 pyogp.lib.base.utilities 1 1 100% pyogp.lib.base.utilities.events 35 17 48% 36-38, 43-51, 55-65, 69, 73-74, 78 pyogp.lib.base.utilities.helpers 107 45 42% 48, 54-58, 64-71, 77-81, 87-100, 106-119, 125-128, 136, 150, 156, 162, 187, 204, 208, 213, 267-268, 270, 275-276, 287-292, 296-305, 309 ---------------------------------------------------------------------- TOTAL 1741 1286 73% ---------------------------------------------------------------------- Ran 71 tests in 17.255s OK
Writing Test Cases
Tests can be written using standard unittest. The tests in pyogp.interop cover some ogp and a couple of legacy cases, these need to be updated to work.
Testing only the call to login.cgi is unique, we don't need to spawn the client in a coroutine, nor do we need to keep the client alive, we just need to post to the login endpoint and evaluate the response.
pyogp.interop.tests.test_legacy_login
<python>import unittest, doctest import ConfigParser from pkg_resources import resource_stream import time import uuid import pprint
from pyogp.lib.base.agent import Agent from pyogp.lib.base.datatypes import UUID from pyogp.lib.base.exc import LoginError from pyogp.lib.base.settings import Settings
import helpers
class AuthLegacyLoginTest(unittest.TestCase):
def setUp(self): # initialize the config self.config = ConfigParser.ConfigParser() self.config.readfp(resource_stream(__name__, 'testconfig.cfg')) self.test_setup_config_name = 'test_interop_account' self.firstname = self.config.get(self.test_setup_config_name, 'firstname') self.lastname = self.config.get(self.test_setup_config_name, 'lastname') self.password = self.config.get(self.test_setup_config_name, 'password') self.agent_id = self.config.get(self.test_setup_config_name, 'agent_id') self.login_uri = self.config.get(self.test_setup_config_name, 'login_uri') self.region = self.config.get('test_interop_regions', 'start_region_uri')
self.successful_login_reponse_params = ['last_name', 'sim_ip', 'inventory-lib-root', 'start_location', 'inventory-lib-owner', 'udp_blacklist', 'home', 'message', 'agent_access_max', 'first_name', 'agent_region_access', 'circuit_code', 'sim_port', 'seconds_since_epoch', 'secure_session_id', 'look_at', 'ao_transition', 'agent_id', 'inventory_host', 'region_y', 'region_x', 'seed_capability', 'agent_access', 'session_id', 'login']
self.settings = Settings() self.settings.MULTIPLE_SIM_CONNECTIONS = False
self.client = Agent(self.settings, self.firstname, self.lastname, self.password)
def tearDown(self): if self.client.connected: self.client.logout() def test_base_login(self): """ login with an account which should just work """
self.client.settings.ENABLE_INVENTORY_MANAGEMENT = False
self.client.login(loginuri = self.login_uri, start_location = self.region, connect_region = False)
# Make sure that the login response attributes propagate properly, # Make sure the login against a grid has worked self.assertEquals(self.client.grid_type, 'Legacy', 'Storing the wrong grid type based on a \'legacy\' login request') self.assertEquals(self.client.firstname, self.firstname) self.assertEquals(self.client.lastname, self.lastname) self.assertEquals(self.client.lastname, self.lastname) self.assertEquals(self.client.name, self.firstname + ' ' + self.lastname) self.assertEquals(self.client.connected, True) self.assertNotEquals(self.client.agent_id, None) self.assertEquals(str(self.client.agent_id), self.agent_id) self.assertNotEquals(self.client.session_id, None) self.assertNotEquals(self.client.secure_session_id, None)
self.assertEquals(self.client.login_response['last_name'], self.lastname) self.assertEquals(self.client.login_response['first_name'], '"' + self.firstname + '"') self.assertEquals(self.client.login_response['login'], 'true') self.assertEquals(self.client.login_response['secure_session_id'], str(self.client.secure_session_id)) self.assertEquals(self.client.login_response['session_id'], str(self.client.session_id)) self.assertEquals(self.client.login_response['agent_id'], str(self.client.agent_id)) self.assertNotEquals(self.client.login_response['seed_capability'], )
fail = 0 fail_extra = 0 fail_missing = 0 extra_keys = missing_keys = for key in self.client.login_response: try: self.successful_login_reponse_params.index(key) # if the key is in our valid list, sweet except: fail_extra = 1 extra_keys = extra_keys + ' ' + key
for key in self.client.login_response: try: self.successful_login_reponse_params.index(key) # if the key is in our valid list, sweet except: fail_missing = 1 missing_keys = missing_keys + ' ' + key self.assertEquals(fail_extra, 0, 'login response has additional keys: ' + extra_keys) self.assertEquals(fail_missing, 0, 'login response is missing keys: ' + missing_keys)
def test_login_with_bad_password(self):
self.client.settings.ENABLE_INVENTORY_MANAGEMENT = False
self.assertRaises(LoginError, self.client.login, loginuri = self.login_uri, password = 'BadPassword', start_location = self.region, connect_region = False)
def test_suite():
from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(AuthLegacyLoginTest)) return suite</python>