Difference between revisions of "PyOGP Package Unittests"
Enus Linden (talk | contribs) |
Enus Linden (talk | contribs) |
||
Line 1: | Line 1: | ||
{{PyOGP/navigation}} | |||
== Overview == | == Overview == | ||
Latest revision as of 09:52, 25 June 2010
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.lib.client.test.test_agent
This test is a standard unittest instance, which tests agent.py at some level of coverage, and also shows how to use some of the mock objects available for use in testing the libs without connecting to a real grid. The mocks should likely be reworked to be simplified and standardized, but are functional for now.
<python># standard python libs import unittest
- pyogp
from pyogp.lib.client.agent import Agent, Home from pyogp.lib.client.login import LegacyLoginParams, OGPLoginParams from pyogp.lib.client.exc import LoginError
- pyogp tests
from pyogp.lib.base.tests.mock_xmlrpc import MockXMLRPC from pyogp.lib.base.tests.base import MockXMLRPCLogin, MockAgentDomainLogin from pyogp.lib.base.network.tests.mockup_client import MockupClient import pyogp.lib.base.tests.config
class TestAgent(unittest.TestCase):
def setUp(self):
self.legacy_loginuri = 'http://localhost:12345/cgi-bin/login.cgi' self.ogp_loginuri = 'http://localhost:12345/auth.cgi' self.firstname = 'firstname' self.lastname = 'lastname' self.password = 'secret'
self.client = Agent()
def tearDown(self):
pass
def test_agent_legacy_login_via_variables(self):
# override the network client with the mock client pointed at the mock login handler self.loginhandler = MockXMLRPC(MockXMLRPCLogin(), self.legacy_loginuri)
self.client.login(self.legacy_loginuri, self.firstname, self.lastname, self.password, start_location = 'start', handler = self.loginhandler, connect_region = False)
self.assertEquals(self.client.login_response, {'region_y': '256', 'region_x': '256', 'first_name': '"first"', 'secure_session_id': '00000000-0000-0000-0000-000000000000', 'sim_ip': '127.0.0.1', 'agent_access': 'M', 'circuit_code': '600000000', 'look_at': '[r0.9963859999999999939,r-0.084939700000000006863,r0]', 'session_id': '00000000-0000-0000-0000-000000000000', 'udp_blacklist': 'EnableSimulator,TeleportFinish,CrossedRegion', 'seed_capability': 'https://somesim:12043/cap/00000000-0000-0000-0000-000000000000', 'agent_id': '00000000-0000-0000-0000-000000000000', 'last_name': 'last', 'inventory_host': 'someinvhost', 'start_location': 'last', 'sim_port': '13001', 'message': 'message', 'login': 'true', 'seconds_since_epoch': '1234567890'})
def test_agent_legacy_login_via_params(self):
# override the network client with the mock client pointed at the mock login handler self.loginhandler = MockXMLRPC(MockXMLRPCLogin(), self.legacy_loginuri)
login_params = LegacyLoginParams(self.firstname, self.lastname, self.password)
self.client.login(self.legacy_loginuri, login_params = login_params, start_location = 'start', handler = self.loginhandler, connect_region = False)
self.assertEquals(self.client.login_response, {'region_y': '256', 'region_x': '256', 'first_name': '"first"', 'secure_session_id': '00000000-0000-0000-0000-000000000000', 'sim_ip': '127.0.0.1', 'agent_access': 'M', 'circuit_code': '600000000', 'look_at': '[r0.9963859999999999939,r-0.084939700000000006863,r0]', 'session_id': '00000000-0000-0000-0000-000000000000', 'udp_blacklist': 'EnableSimulator,TeleportFinish,CrossedRegion', 'seed_capability': 'https://somesim:12043/cap/00000000-0000-0000-0000-000000000000', 'agent_id': '00000000-0000-0000-0000-000000000000', 'last_name': 'last', 'inventory_host': 'someinvhost', 'start_location': 'last', 'sim_port': '13001', 'message': 'message', 'login': 'true', 'seconds_since_epoch': '1234567890'})
def test_agent_ogp_login_via_variables(self):
# override the network client with the mock client pointed at the mock login handler self.loginhandler = MockupClient(MockAgentDomainLogin())
self.client.login(self.ogp_loginuri, self.firstname, self.lastname, self.password, start_location = 'start', handler = self.loginhandler, connect_region = False)
self.assertEquals(self.client.login_response, {'agent_seed_capability': 'http://127.0.0.1:12345/seed_cap', 'authenticated': True})
def test_agent_ogp_login_via_params(self):
# override the network client with the mock client pointed at the mock login handler self.loginhandler = MockupClient(MockAgentDomainLogin())
login_params = OGPLoginParams(self.firstname, self.lastname, self.password)
self.client.login(self.ogp_loginuri, self.firstname, self.lastname, self.password, start_location = 'start', handler = self.loginhandler, connect_region = False)
self.assertEquals(self.client.login_response, {'agent_seed_capability': 'http://127.0.0.1:12345/seed_cap', 'authenticated': True})
def test_agent_login_no_account_info(self):
self.assertRaises(LoginError, self.client.login, self.ogp_loginuri)
def test_legacy_get_login_params(self):
self.client.grid_type = 'Legacy' params = self.client._get_login_params(self.firstname, self.lastname, self.password)
self.assertEquals(type(params), type(LegacyLoginParams(self.firstname, self.lastname, self.password)))
def test_ogp_get_login_params(self):
self.client.grid_type = 'OGP' params = self.client._get_login_params(self.firstname, self.lastname, self.password)
self.assertEquals(type(params), type(OGPLoginParams(self.firstname, self.lastname, self.password)))
def test_failed_legacy_login(self):
# ToDo: enable mne when you can get me working, it's 'correct', # but not raising the error properly?
self.password = 'badpassword'
# override the network client with the mock client pointed at the mock login handler self.loginhandler = MockXMLRPC(MockXMLRPCLogin(), self.legacy_loginuri)
self.assertRaises(LoginError, self.client.login, self.legacy_loginuri, self.firstname, self.lastname, self.password, start_location = 'start', handler = self.loginhandler)
def test_agent_home_class(self):
home_string = "{'region_handle':[r261120, r247040], 'position':[r171.622, r148.26, r79.3938], 'look_at':[r0, r1, r0]}"
home = Home(home_string)
# Note: have not yet worked out precision on floats. Kinda need to self.assertEquals(home.region_handle, [261120, 247040]) self.assertEquals(home.position.X, 171.62200000000001) self.assertEquals(home.position.Y, 148.25999999999999) self.assertEquals(home.position.Z, 79.393799999999999) self.assertEquals(home.look_at.X, 0) self.assertEquals(home.look_at.Y, 1) self.assertEquals(home.look_at.Z, 0) self.assertEquals(home.global_x, 261120) self.assertEquals(home.global_y, 247040)
def test_suite():
from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestAgent)) return suite</python>