# -*- test-case-name: mv3d.test.test_factory -*- # Copyright (C) 2009-2012 Mortal Coil Games # See LICENSE for details. """ The test code for factories. A factory can generate objects, areas, etc in some form. This may or may not involve the input of a user. """ import os import ode from zope.interface import implements from twisted.internet import defer from twisted.internet.defer import inlineCallbacks from twisted.trial.unittest import TestCase from mv3d.server.factory import ( Template, coerceClass, coerceFactory, coerceTuple, IFactory, filterAndOrderParameters, Object, BodyFactory, Collider, BasicVob, Random, Chain, OffsetComposite, coerceQuaternionFromEuler, ) from mv3d.util.conductor import IConductor from mv3d.test.mixins import WithDocTests from mv3d.util.classgen import ClassGenerator from mv3d.util.math3d import Quaternion, Vector from mv3d.phys.opende import ODEPhysics class FakeObject: """ FAKER """ body = None realm = None name = None iid = (1, 123) parent = None classname = None def __init__(self, cgen=None): if cgen is not None: self.classname = cgen.classname self.perms = [] self.vobs = [] self.colliders = [] def setParent(self, parent): """ set it """ self.parent = parent self.body.parent = parent def setBody(self, body): """ Set it """ self.body = body def create(self, realm): """ ok """ self.realm = realm def setName(self, name): """ set it """ self.name = name def getID(self): """ return it """ return self.iid def grantPermission(self, what, who): """ store it """ self.perms.append((what, who)) def getBoundingBox(self): """ return it """ return (-5, -5, -5, 5, 5, 5) def addVisualObject(self, vob): """ add it """ self.vobs.append(vob) def addCollider(self, col): """ add it """ self.colliders.append(col) class FakeConductor: """ Just a faker """ implements(IConductor) def __init__(self, filename=None): """ do nothing """ self.filename = filename self.realm = self self.world = ODEPhysics() def getLocalService(self, _stype): """ return self """ return self def acquireAsset(self, _aid): """ return self """ return defer.succeed(self) def getFullFile(self): """ return our filename """ return self.filename def getItem(self, _iid): """ fake it """ return defer.succeed(self) def newObject(self, classgen): """ create a fake one """ return defer.succeed(FakeObject(classgen)) def takeOut(self, _iid): """ do nothing """ def contain(self, _iid): """ do nothing """ class CoercionTests(TestCase): """ Test the various coercion functions """ def test_coerceFactory(self): """ Test parsing a single factory """ self.assertEqual(coerceFactory("[box]"), ["box"]) def test_parseFactoryOptionBad(self): """ Test that parsing a bad factory option results in a ValueError """ self.assertRaises(ValueError, coerceFactory, "oops") self.assertRaises(ValueError, coerceFactory, "[oops") self.assertRaises(ValueError, coerceFactory, "oops]") def test_parseFactoryOptionMulti(self): """ Tests parsing multiple factories """ self.assertEqual(coerceFactory("[fact1, fact2 ,fact3 ]"), ["fact1", "fact2", "fact3"]) def test_parseClassOption(self): """ Test parsing a simple class option """ result = coerceClass("this.is.a.class") self.assertEqual(result.classname, "class") self.assertEqual(result.modulename, "this.is.a") def test_parseClassOptionAsset(self): """ Test parseClassOption with an asset """ result = coerceClass("(0, 12)") self.assertEqual(result.assetid, (0, 12)) def test_filterAndOrderParameters(self): """ Do a basic test of filterAndOrderParameters """ result = filterAndOrderParameters( dict(test=dict(hello=123))) self.assertEqual(result, [dict(name="test", hello=123)]) def test_filterAndOrderParametersDefaultFilter(self): """ Do a basic test of filterAndOrderParameters """ result = filterAndOrderParameters( dict(test=dict(hello=123, coerce=666))) self.assertEqual(result, [dict(name="test", hello=123)]) def test_filterAndOrderParametersFilter(self): """ Do a basic test of filterAndOrderParameters """ result = filterAndOrderParameters( dict(test=dict(hello=123)), filters=["hello"]) self.assertEqual(result, [dict(name="test")]) def test_filterAndOrderParametersOrder(self): """ Do a basic test of filterAndOrderParameters """ result = filterAndOrderParameters( dict(test=dict(hello=123), best=dict(hello=23)), order=["test", "best"]) self.assertEqual(result, [dict(name="test", hello=123), dict(name="best", hello=23)]) def test_coerceTuple(self): """ Test coercing a string to a tuple """ result = coerceTuple("(0,1, 2,3 , 4)") self.assertEqual(result, (0, 1, 2, 3, 4)) def test_coerceTupleBad(self): """ Test coercing a malformed string to a tuple """ self.assertRaises(ValueError, coerceTuple, "0,1, 2,3 , 4)") self.assertRaises(ValueError, coerceTuple, "0,1, 2,3") self.assertRaises(ValueError, coerceTuple, "(0,1, 2,3") def test_coerceTupleFloat(self): """ Test coercing a string to a tuple of floats """ result = coerceTuple("(0.12,1.23, 2.45,3.67 , 4.78)", float) self.assertEqual(result, (0.12, 1.23, 2.45, 3.67, 4.78)) def assertTupleAlmostEqual(self, val1, val2, places=2): """ Assert that the two tuples are equal """ if not isinstance(val1, tuple): self.fail("assertTupleAlmostEqual: Not a tuple1: %r" % val1) if not isinstance(val2, tuple): self.fail("assertTupleAlmostEqual: Not a tuple2: %r" % val2) if not len(val1) == len(val2): self.fail("assertTupleAlmostEqual: Length not equal!") for val in range(len(val1)): self.assertAlmostEqual(val1[val], val2[val], places, "%r != %r to %d places" % (val1, val2, places)) def test_coerceQuaternionFromEuler(self): """ Test getting a quaternion from a euler value """ self.assertEqual(tuple(coerceQuaternionFromEuler("(0, 0, 0)")), (1, 0, 0, 0)) self.assertTupleAlmostEqual(tuple(coerceQuaternionFromEuler( "(0, 1.57, 0)")), (0.71, 0, 0.71, 0)) def test_coerceQuaternionFromEulerQuat(self): """ Test getting a quaternion from a euler value """ self.assertEqual(tuple(coerceQuaternionFromEuler(Quaternion())), (1, 0, 0, 0)) def test_coerceQuaternionFromEuler4Tuple(self): """ Test getting a quaternion from a euler value """ self.assertEqual(tuple(coerceQuaternionFromEuler((1, 0, 0, 0))), (1, 0, 0, 0)) def test_coerceQuaternionFromEulerVector(self): """ Test getting a quaternion from a euler value """ self.assertEqual(tuple(coerceQuaternionFromEuler(Vector())), (1, 0, 0, 0)) self.assertTupleAlmostEqual(tuple(coerceQuaternionFromEuler( Vector(0, 1.57, 0))), (0.71, 0, 0.71, 0)) def test_coerceQuaternionFromEuler3Tuple(self): """ Test getting a quaternion from a euler value """ self.assertEqual(tuple(coerceQuaternionFromEuler((0, 0, 0))), (1, 0, 0, 0)) self.assertTupleAlmostEqual(tuple(coerceQuaternionFromEuler( (0, 1.57, 0))), (0.71, 0, 0.71, 0)) class BasicFactory: """ A simple factory """ implements(IFactory) parameterInfo = dict( anint=dict(coerce=int), afactory=dict(type="factory"), smart=dict(coerce=str, type="String", value="hello") ) products = 0 def __init__(self, parent): self.parent = parent def giveInput(self, options): """ process input Please don't use this as an example of a good way to implement this function! Using setattr like this is dangerous outside a test env. """ for key, value in options.items(): setattr(self, key, value) def getParameterInfo(self, parameter): """ just return the parameter info for this param """ return self.parameterInfo[parameter] def produce(self): """ Inc the products """ self.products += 1 return self.products class TemplateTests(TestCase, WithDocTests): """ Test the Template factory """ checkItems = (Template,) def setUp(self): """ Make a test template """ self.testTemplate = """[factory] name=box wall parts=[wall] [wall] factory=mv3d.test.test_factory.BasicFactory requireInput=something [box] factory=mv3d.test.test_factory.BasicFactory anInt=98 aFactory=[wall] [input] factory=mv3d.test.test_factory.BasicFactory requireInput=anint [input2] factory=mv3d.test.test_factory.BasicFactory requireInput=smart """ self.template = Template(None) self.template.view_setTemplate(None, self.testTemplate) self.temp = self.mktemp() os.makedirs(self.temp) tfile = open(os.path.join(self.temp, "template"), "w") tfile.write(self.testTemplate) tfile.close() def test_setTemplate(self): """ Test setting the template """ templ = Template(None) templ.view_setTemplate(None, self.testTemplate) self.failUnless(templ.template.has_section("wall")) def test_requiresInput(self): """ Test to see if requiring input works correctly """ self.failUnless(self.template.requiresInput()) templ = Template(None) templ.template.add_section("section") self.failIf(templ.requiresInput()) @inlineCallbacks def test_setTemplateAsset(self): """ Test setting the template from an asset """ templ = Template(FakeConductor(os.path.join(self.temp, "template"))) yield templ.setTemplateAsset(123) self.failUnless(templ.template.has_section("wall")) @inlineCallbacks def test_buildFactory(self): """ Test building a factory """ factory = yield self.template.buildFactory(None, "wall") self.failUnless(isinstance(factory, BasicFactory)) self.assertEqual(factory.products, 0) @inlineCallbacks def test_produce(self): """ Test producing our simple factory """ result = yield self.template.produce() self.assertEqual(result, [1]) self.assertEqual(self.template.factories["wall"].products, 1) @inlineCallbacks def test_parseValue(self): """ Test parsing a simple value """ result = yield self.template.parseValue(BasicFactory(None), "anint", "34") self.assertEqual(result, 34) @inlineCallbacks def test_parseValueFactory(self): """ Test parsing a value that points to a factory """ fact = BasicFactory(None) result = yield self.template.parseValue(fact, "afactory", "[wall]") self.assertEqual(len(result), 1) self.failUnless(isinstance(result[0], BasicFactory)) self.assertEqual(result[0].products, 0) self.assertNotIdentical(result[0], fact) @inlineCallbacks def test_setFactoryValues(self): """ Test the setFactoryValues method """ self.template.factories["box"] = BasicFactory(None) yield self.template.setFactoryValues("box") self.assertEqual(self.template.factories["box"].anint, 98) self.assertEqual(len(self.template.factories["box"].afactory), 1) def test_gatherInputRequirements(self): """ Test the gatherInputRequirements method """ self.template.factories["input"] = BasicFactory(None) result = self.template.gatherInputRequirements("input") self.assertEqual(result, dict(anint=dict(coerce=int))) @inlineCallbacks def test_getInputSections(self): """ Test the getInputSections method """ self.template.factories["input"] = BasicFactory(None) self.template.factories["input2"] = BasicFactory(None) self.template.built = True result = yield self.template.view_getInputSections(None) self.assertEqual(result, {'input': [{'name': 'anint'}], 'input2': [{'name': 'smart', 'type': 'String', 'value': 'hello'}]}) class ObjectTests(TestCase, WithDocTests): """ Test the Object factory """ checkItems = (Object,) @inlineCallbacks def test_produce(self): """ Test producing an object """ obj = Object(FakeConductor()) obj.body = [BodyFactory(obj)] bob = yield obj.produce() self.assertEqual(bob.classname, "BasicPhysicalObject") self.assertEqual(bob.body.parent, obj.parent) class BodyFactoryTests(TestCase, WithDocTests): """ Test the Body factory """ checkItems = (BodyFactory,) @inlineCallbacks def test_produce(self): """ Test producing a body """ bod = BodyFactory(None) body = yield bod.produce() self.assertEqual(body.__class__.__name__, "MobileBody") @inlineCallbacks def test_produceStationary(self): """ Test producing a body """ bod = BodyFactory(None) bod.mobile = False body = yield bod.produce() self.assertEqual(body.__class__.__name__, "StationaryBody") class ColliderTests(TestCase, WithDocTests): """ Test the Collider factory """ checkItems = (Collider,) def test_produce(self): """ Test producing a collider """ col = Collider(FakeObject()) col.shape = "box" col.position = (0, 0, 0) col.size = (1, 1, 1) col.density = 1 box = col.produce() self.assertEqual(box.__class__.__name__, "ODEBox") self.assertEqual(len(col.parent.colliders), 1) class BasicVobTests(TestCase, WithDocTests): """ Test the Basic vob factory """ checkItems = (BasicVob,) def test_produce(self): """ Test producing a vob """ vob = BasicVob(FakeObject()) result = vob.produce() self.assertEqual(result.__class__.__name__, "BasicVisualObject") self.assertEqual(len(vob.parent.vobs), 1) class FakeFactory: """ FAKE """ def __init__(self, num=0): self.num = num self.inputs = [] self.produceCount = 0 def giveInput(self, inp): """ record it """ self.inputs.append(inp) def produce(self): """ just return our num """ self.produceCount += 1 return self.num class RandomTests(TestCase, WithDocTests): """ Test the random factory """ checkItems = (Random,) def test_produceNothing(self): """ Test producing with the default values """ ran = Random(None) result = ran.produce() self.assertIdentical(result, None) @inlineCallbacks def test_produceFactory(self): """ Test producing a single factory """ ran = Random(None) ran.factory = [FakeFactory(1)] ran.resultCountRange = [1, 2] result = yield ran.produce() self.assertEqual(result, [1]) @inlineCallbacks def test_produceFactories(self): """ Test producing 2 factories """ ran = Random(None) ran.factory = [FakeFactory(2)] ran.resultCountRange = [2, 3] result = yield ran.produce() self.assertEqual(result, [2, 2]) def test_produceNumber(self): """ Test producing a number """ ran = Random(None) ran.numberRange = [5, 8] ran.resultCountRange = [1, 2] result = ran.produce() self.failUnless(result > 5) self.failUnless(result < 8) def test_produceNumbers(self): """ Test producing 10 numbers """ ran = Random(None) ran.numberRange = [2, 9] ran.resultCountRange = [10, 11] results = ran.produce() self.assertEqual(len(results), 10) for result in results: self.failUnless(result > 2) self.failUnless(result < 9) def test_produceSomeNumbers(self): """ Test producing 5 - 10 numbers """ ran = Random(None) ran.numberRange = [2, 9] ran.resultCountRange = [5, 11] results = ran.produce() self.failUnless(len(results) >= 5, str(len(results))) self.failUnless(len(results) <= 10, str(len(results))) for result in results: self.failUnless(result > 2) self.failUnless(result < 9) def test_produceString(self): """ Test producing a string """ ran = Random(None) ran.strings = ["hello"] ran.resultCountRange = [1, 2] result = ran.produce() self.assertEqual(result, "hello") def test_produceStrings(self): """ Test producing 10 strings """ ran = Random(None) ran.strings = ["hello"] ran.resultCountRange = [10, 11] results = ran.produce() self.assertEqual(len(results), 10) for result in results: self.assertEqual(result, "hello") def test_produceSomeStrings(self): """ Test producing 5 - 10 strings """ ran = Random(None) ran.strings = ["hello", "goodbye"] ran.resultCountRange = [5, 11] results = ran.produce() self.failUnless(len(results) >= 5, str(len(results))) self.failUnless(len(results) <= 10, str(len(results))) for result in results: self.failUnless(result == "hello" or result == "goodbye") def test_producePoint(self): """ Test producing a point """ ran = Random(None) ran.boundingBox = (-10, -10, -10, 10, 10, 10) ran.resultCountRange = [1, 2] result = ran.produce() for coord in result: self.failUnless(coord < 10) self.failUnless(coord > -10) def test_produceId(self): """ Test producing an id """ ran = Random(None) ran.idList = [1234] ran.resultCountRange = [1, 2] result = ran.produce() self.assertEqual(result, 1234) def test_produceIds(self): """ Test producing two ids """ ran = Random(None) ran.idList = [1234] ran.resultCountRange = [2, 3] result = ran.produce() self.assertEqual(result, [1234, 1234]) class ChainTests(TestCase, WithDocTests): """ Test the chain factory """ checkItems = (Chain,) @inlineCallbacks def test_produce(self): """ Test producing a chain """ cha = Chain(None) cha.position = (20, 30, 40) cha.axis = "y" cha.count = 5 cha.items = [FakeFactory(FakeObject(ClassGenerator("mv3d.bob")))] cha.spaceBetween = 2 objects = yield cha.produce() self.assertEqual(len(objects), 5) self.assertEqual(cha.items[0].produceCount, 5) for num, inp in enumerate(cha.items[0].inputs): self.assertEqual(inp["name"], "Chain%d" % num) self.assertEqual(list(inp["position"]), [20, 30 + num * 2 + num * 10, 40]) class OffsetChild: """ fake factory """ producedPos = None producedRot = None parent = None def __init__(self, pos, rot): self.position = pos self.rotation = rot def produce(self): """ record the rot and pos """ self.producedPos = self.position self.producedRot = self.rotation class OffsetCompositeTests(TestCase, WithDocTests): """ Test the OffsetComposite class """ checkItems = (OffsetComposite,) @inlineCallbacks def test_produce(self): """ Test producing an offsetcomposite """ ofc = OffsetComposite(1234) ofc.children = [OffsetChild((0, 0, 0), (1, 0, 0, 0)), OffsetChild((10, 10, 10), (0.4, 0.1, 0, 0))] ofc.position = (5, 5, 5) ofc.rotation = (0.2, 0, 0.1, 0) res = yield ofc.produce() self.assertEqual(res, 1234) self.assertEqual(tuple(ofc.children[0].producedPos), (5, 5, 5)) self.assertEqual(tuple(ofc.children[1].producedPos), (15, 15, 15)) self.assertEqual(tuple(ofc.children[0].position), (0, 0, 0)) self.assertEqual(tuple(ofc.children[1].position), (10, 10, 10)) self.assertEqual(tuple(ofc.children[0].rotation), (1, 0, 0, 0)) self.assertEqual(tuple(ofc.children[1].rotation), (0.4, 0.1, 0, 0)) self.assertEqual(tuple(ofc.children[0].producedRot), (0.2, 0, 0.1, 0)) self.assertAlmostEqual(ofc.children[1].producedRot[0], 0.08, 2) self.assertAlmostEqual(ofc.children[1].producedRot[1], 0.02, 2) self.assertAlmostEqual(ofc.children[1].producedRot[2], 0.04, 2) self.assertAlmostEqual(ofc.children[1].producedRot[3], -0.01, 2) self.assertEqual(ofc.children[0].parent, 1234) self.assertEqual(ofc.children[1].parent, 1234)