#!/usr/bin/env python
#
# LSST Data Management System
# Copyright 2008, 2009, 2010 LSST Corporation.
#
# This product includes software developed by the
# LSST Project (http://www.lsst.org/).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the LSST License Statement and
# the GNU General Public License along with this program. If not,
# see <http://www.lsstcorp.org/LegalNotices/>.
#
import io
import itertools
import re
import os
import unittest
import lsst.utils.tests
import lsst.pex.config as pexConfig
import pickle
GLOBAL_REGISTRY = {}
class Simple(pexConfig.Config):
i = pexConfig.Field("integer test", int, optional=True)
f = pexConfig.Field("float test", float, default=3.0)
b = pexConfig.Field("boolean test", bool, default=False, optional=False)
c = pexConfig.ChoiceField("choice test", str, default="Hello",
allowed={"Hello": "First choice", "World": "second choice"})
r = pexConfig.RangeField("Range test", float, default=3.0, optional=False,
min=3.0, inclusiveMin=True)
ll = pexConfig.ListField("list test", int, default=[1, 2, 3], maxLength=5,
itemCheck=lambda x: x is not None and x > 0)
d = pexConfig.DictField("dict test", str, str, default={"key": "value"},
itemCheck=lambda x: x.startswith('v'))
n = pexConfig.Field("nan test", float, default=float("NAN"))
GLOBAL_REGISTRY["AAA"] = Simple
class InnerConfig(pexConfig.Config):
f = pexConfig.Field("Inner.f", float, default=0.0, check=lambda x: x >= 0, optional=False)
GLOBAL_REGISTRY["BBB"] = InnerConfig
class OuterConfig(InnerConfig, pexConfig.Config):
i = pexConfig.ConfigField("Outer.i", InnerConfig)
def __init__(self):
pexConfig.Config.__init__(self)
self.i.f = 5.0
def validate(self):
pexConfig.Config.validate(self)
if self.i.f < 5:
raise ValueError("validation failed, outer.i.f must be greater than 5")
class Complex(pexConfig.Config):
c = pexConfig.ConfigField("an inner config", InnerConfig)
r = pexConfig.ConfigChoiceField("a registry field", typemap=GLOBAL_REGISTRY,
default="AAA", optional=False)
p = pexConfig.ConfigChoiceField("another registry", typemap=GLOBAL_REGISTRY,
default="BBB", optional=True)
class ConfigTest(unittest.TestCase):
def setUp(self):
self.simple = Simple()
self.inner = InnerConfig()
self.outer = OuterConfig()
self.comp = Complex()
def tearDown(self):
del self.simple
del self.inner
del self.outer
del self.comp
def testInit(self):
self.assertEqual(self.simple.i, None)
self.assertEqual(self.simple.f, 3.0)
self.assertEqual(self.simple.b, False)
self.assertEqual(self.simple.c, "Hello")
self.assertEqual(list(self.simple.ll), [1, 2, 3])
self.assertEqual(self.simple.d["key"], "value")
self.assertEqual(self.inner.f, 0.0)
self.assertEqual(self.outer.i.f, 5.0)
self.assertEqual(self.outer.f, 0.0)
self.assertEqual(self.comp.c.f, 0.0)
self.assertEqual(self.comp.r.name, "AAA")
self.assertEqual(self.comp.r.active.f, 3.0)
self.assertEqual(self.comp.r["BBB"].f, 0.0)
def testValidate(self):
self.simple.validate()
self.inner.validate()
self.assertRaises(ValueError, setattr, self.outer.i, "f", -5)
self.outer.i.f = 10.
self.outer.validate()
try:
self.simple.d["failKey"] = "failValue"
123 ↛ 125line 123 didn't jump to line 125 except pexConfig.FieldValidationError:
pass
except Exception:
raise "Validation error Expected"
self.simple.validate()
self.outer.i = InnerConfig
self.assertRaises(ValueError, self.outer.validate)
self.outer.i = InnerConfig()
self.assertRaises(ValueError, self.outer.validate)
self.comp.validate()
self.comp.r = None
self.assertRaises(ValueError, self.comp.validate)
self.comp.r = "BBB"
self.comp.validate()
def testRangeFieldConstructor(self):
"""Test RangeField constructor's checking of min, max
"""
val = 3
self.assertRaises(ValueError, pexConfig.RangeField, "", int, default=val, min=val, max=val-1)
self.assertRaises(ValueError, pexConfig.RangeField, "", float, default=val, min=val, max=val-1e-15)
for inclusiveMin, inclusiveMax in itertools.product((False, True), (False, True)):
if inclusiveMin and inclusiveMax:
# should not raise
class Cfg1(pexConfig.Config):
r1 = pexConfig.RangeField(doc="", dtype=int,
default=val, min=val, max=val, inclusiveMin=inclusiveMin,
inclusiveMax=inclusiveMax)
r2 = pexConfig.RangeField(doc="", dtype=float,
default=val, min=val, max=val, inclusiveMin=inclusiveMin,
inclusiveMax=inclusiveMax)
Cfg1()
else:
# raise while constructing the RangeField (hence cannot make it part of a Config)
self.assertRaises(ValueError, pexConfig.RangeField, doc="", dtype=int,
default=val, min=val, max=val, inclusiveMin=inclusiveMin,
inclusiveMax=inclusiveMax)
self.assertRaises(ValueError, pexConfig.RangeField, doc="", dtype=float,
default=val, min=val, max=val, inclusiveMin=inclusiveMin,
inclusiveMax=inclusiveMax)
def testRangeFieldDefault(self):
"""Test RangeField's checking of the default value
"""
minVal = 3
maxVal = 4
for val, inclusiveMin, inclusiveMax, shouldRaise in (
(minVal, False, True, True),
(minVal, True, True, False),
(maxVal, True, False, True),
(maxVal, True, True, False),
):
class Cfg1(pexConfig.Config):
r = pexConfig.RangeField(doc="", dtype=int,
default=val, min=minVal, max=maxVal,
inclusiveMin=inclusiveMin, inclusiveMax=inclusiveMax)
class Cfg2(pexConfig.Config):
r2 = pexConfig.RangeField(doc="", dtype=float,
default=val, min=minVal, max=maxVal,
inclusiveMin=inclusiveMin, inclusiveMax=inclusiveMax)
186 ↛ 187line 186 didn't jump to line 187, because the condition on line 186 was never true if shouldRaise:
self.assertRaises(pexConfig.FieldValidationError, Cfg1)
self.assertRaises(pexConfig.FieldValidationError, Cfg2)
else:
Cfg1()
Cfg2()
def testSave(self):
self.comp.r = "BBB"
self.comp.p = "AAA"
self.comp.c.f = 5.
self.comp.save("roundtrip.test")
roundTrip = Complex()
roundTrip.load("roundtrip.test")
os.remove("roundtrip.test")
self.assertEqual(self.comp.c.f, roundTrip.c.f)
self.assertEqual(self.comp.r.name, roundTrip.r.name)
del roundTrip
# test saving to an open file
outfile = open("roundtrip.test", "w")
self.comp.saveToStream(outfile)
outfile.close()
roundTrip = Complex()
roundTrip.load("roundtrip.test")
os.remove("roundtrip.test")
self.assertEqual(self.comp.c.f, roundTrip.c.f)
self.assertEqual(self.comp.r.name, roundTrip.r.name)
# test backwards compatibility feature of allowing "root" instead of "config"
outfile = open("roundtrip.test", "w")
self.comp.saveToStream(outfile, root="root")
outfile.close()
roundTrip = Complex()
roundTrip.load("roundtrip.test")
os.remove("roundtrip.test")
self.assertEqual(self.comp.c.f, roundTrip.c.f)
self.assertEqual(self.comp.r.name, roundTrip.r.name)
def testDuplicateRegistryNames(self):
self.comp.r["AAA"].f = 5.0
self.assertEqual(self.comp.p["AAA"].f, 3.0)
def testInheritance(self):
class AAA(pexConfig.Config):
a = pexConfig.Field("AAA.a", int, default=4)
class BBB(AAA):
b = pexConfig.Field("BBB.b", int, default=3)
class CCC(BBB):
c = pexConfig.Field("CCC.c", int, default=2)
# test multi-level inheritance
c = CCC()
self.assertEqual("a" in c.toDict(), True)
self.assertEqual(c._fields["a"].dtype, int)
self.assertEqual(c.a, 4)
# test conflicting multiple inheritance
class DDD(pexConfig.Config):
a = pexConfig.Field("DDD.a", float, default=0.0)
class EEE(DDD, AAA):
pass
e = EEE()
self.assertEqual(e._fields["a"].dtype, float)
self.assertEqual("a" in e.toDict(), True)
self.assertEqual(e.a, 0.0)
class FFF(AAA, DDD):
pass
f = FFF()
self.assertEqual(f._fields["a"].dtype, int)
self.assertEqual("a" in f.toDict(), True)
self.assertEqual(f.a, 4)
# test inheritance from non Config objects
class GGG:
a = pexConfig.Field("AAA.a", float, default=10.)
class HHH(GGG, AAA):
pass
h = HHH()
self.assertEqual(h._fields["a"].dtype, float)
self.assertEqual("a" in h.toDict(), True)
self.assertEqual(h.a, 10.0)
# test partial Field redefinition
class III(AAA):
pass
III.a.default = 5
self.assertEqual(III.a.default, 5)
self.assertEqual(AAA.a.default, 4)
def testConvert(self):
pol = pexConfig.makePolicy(self.simple)
self.assertEqual(pol.exists("i"), False)
self.assertEqual(pol.get("f"), self.simple.f)
self.assertEqual(pol.get("b"), self.simple.b)
self.assertEqual(pol.get("c"), self.simple.c)
self.assertEqual(pol.getArray("ll"), list(self.simple.ll))
ps = pexConfig.makePropertySet(self.simple)
self.assertEqual(ps.exists("i"), False)
self.assertEqual(ps.getScalar("f"), self.simple.f)
self.assertEqual(ps.getScalar("b"), self.simple.b)
self.assertEqual(ps.getScalar("c"), self.simple.c)
self.assertEqual(list(ps.getArray("ll")), list(self.simple.ll))
pol = pexConfig.makePolicy(self.comp)
self.assertEqual(pol.get("c.f"), self.comp.c.f)
ps = pexConfig.makePropertySet(self.comp)
self.assertEqual(ps.getScalar("c.f"), self.comp.c.f)
def testFreeze(self):
self.comp.freeze()
self.assertRaises(pexConfig.FieldValidationError, setattr, self.comp.c, "f", 10.0)
self.assertRaises(pexConfig.FieldValidationError, setattr, self.comp, "r", "AAA")
self.assertRaises(pexConfig.FieldValidationError, setattr, self.comp, "p", "AAA")
self.assertRaises(pexConfig.FieldValidationError, setattr, self.comp.p["AAA"], "f", 5.0)
def checkImportRoundTrip(self, importStatement, searchString, shouldBeThere):
self.comp.c.f = 5.
# Generate a Config through loading
stream = io.StringIO()
stream.write(str(importStatement))
self.comp.saveToStream(stream)
roundtrip = Complex()
roundtrip.loadFromStream(stream.getvalue())
self.assertEqual(self.comp.c.f, roundtrip.c.f)
# Check the save stream
stream = io.StringIO()
roundtrip.saveToStream(stream)
self.assertEqual(self.comp.c.f, roundtrip.c.f)
streamStr = stream.getvalue()
if shouldBeThere:
self.assertTrue(re.search(searchString, streamStr))
else:
self.assertFalse(re.search(searchString, streamStr))
def testImports(self):
# A module not used by anything else, but which exists
importing = "import lsst.pex.config._doNotImportMe\n"
self.checkImportRoundTrip(importing, importing, True)
def testBadImports(self):
dummy = "somethingThatDoesntExist"
importing = """
try:
import %s
except ImportError:
pass
""" % dummy
self.checkImportRoundTrip(importing, dummy, False)
def testPickle(self):
self.simple.f = 5
simple = pickle.loads(pickle.dumps(self.simple))
self.assertIsInstance(simple, Simple)
self.assertEqual(self.simple.f, simple.f)
self.comp.c.f = 5
comp = pickle.loads(pickle.dumps(self.comp))
self.assertIsInstance(comp, Complex)
self.assertEqual(self.comp.c.f, comp.c.f)
def testCompare(self):
comp2 = Complex()
inner2 = InnerConfig()
simple2 = Simple()
self.assertTrue(self.comp.compare(comp2))
self.assertTrue(comp2.compare(self.comp))
self.assertTrue(self.comp.c.compare(inner2))
self.assertTrue(self.simple.compare(simple2))
self.assertTrue(simple2.compare(self.simple))
self.assertEqual(self.simple, simple2)
self.assertEqual(simple2, self.simple)
outList = []
def outFunc(msg):
outList.append(msg)
simple2.b = True
simple2.ll.append(4)
simple2.d["foo"] = "var"
self.assertFalse(self.simple.compare(simple2, shortcut=True, output=outFunc))
self.assertEqual(len(outList), 1)
del outList[:]
self.assertFalse(self.simple.compare(simple2, shortcut=False, output=outFunc))
output = "\n".join(outList)
self.assertIn("Inequality in b", output)
self.assertIn("Inequality in size for ll", output)
self.assertIn("Inequality in keys for d", output)
del outList[:]
self.simple.d["foo"] = "vast"
self.simple.ll.append(5)
self.simple.b = True
self.simple.f += 1E8
self.assertFalse(self.simple.compare(simple2, shortcut=False, output=outFunc))
output = "\n".join(outList)
self.assertIn("Inequality in f", output)
self.assertIn("Inequality in ll[3]", output)
self.assertIn("Inequality in d['foo']", output)
del outList[:]
comp2.r["BBB"].f = 1.0 # changing the non-selected item shouldn't break equality
self.assertTrue(self.comp.compare(comp2))
comp2.r["AAA"].i = 56 # changing the selected item should break equality
comp2.c.f = 1.0
self.assertFalse(self.comp.compare(comp2, shortcut=False, output=outFunc))
output = "\n".join(outList)
self.assertIn("Inequality in c.f", output)
self.assertIn("Inequality in r['AAA']", output)
self.assertNotIn("Inequality in r['BBB']", output)
def testLoadError(self):
"""Check that loading allows errors in the file being loaded to propagate
"""
self.assertRaises(SyntaxError, self.simple.loadFromStream, "bork bork bork")
self.assertRaises(NameError, self.simple.loadFromStream, "config.f = bork")
def testNames(self):
"""Check that the names() method returns valid keys
Also check that we have the right number of keys, and as they are
all known to be valid we know that we got them all
"""
names = self.simple.names()
self.assertEqual(len(names), 8)
for name in names:
self.assertTrue(hasattr(self.simple, name))
class TestMemory(lsst.utils.tests.MemoryTestCase):
pass
def setup_module(module):
lsst.utils.tests.init()
440 ↛ 441line 440 didn't jump to line 441, because the condition on line 440 was never trueif __name__ == "__main__":
lsst.utils.tests.init()
unittest.main()
|