PyOGP Package Unittests

From Second Life Wiki
Jump to: navigation, search

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

  1. pyogp

from pyogp.lib.client.agent import Agent, Home from pyogp.lib.client.login import LegacyLoginParams, OGPLoginParams from pyogp.lib.client.exc import LoginError

  1. 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>