1 # -*- coding: utf-8 -*-
3 import moe.config as cf
4 from moe.config_parser import *
9 class TestConfig(unittest.TestCase):
14 def parse(s, string, level=0, fname='test'):
15 cp = ConfigParser(string, s.t, fname, level)
21 def var(s, varname, create=True):
22 return s.t.lookup(varname, create=create)
25 return s.var(varname, create=False).value()
27 def eqparse(s, string, *args, **kwargs):
28 "Parse expression `s`, return parts that should be equal to anoter run of `eqparse(s)`."
29 return [(i[0], i[1].operation) for i in s.parse(string, *args, **kwargs)]
32 class TestParser(TestConfig):
33 s1 = r"""a="1";z{b='2';w{S.Tr_an-g.e='"'}};c.d='\n';e="{a}{b}";e+='{c.d}';a+="\"\n\{\}";f+='Z{a.b}';e+=c.d;e+=c;e+=a"""
34 s2 = '\t\n \n ' + s1.replace('=', '= \n ').replace(';', '\t \n\t \n ').replace('+=',' \n += ') + '\n\n '
37 assert len(s.parse(s.s1)) == 11
39 def test_noWS_COMMENT(s):
40 assert s.eqparse(s.s1+'#COMMENT') == s.eqparse(s.s1+'#') == s.eqparse(s.s1+'#\n') == s.eqparse(s.s1+'\n#')
43 assert s.eqparse(s.s2) == s.eqparse(s.s1)
45 def test_manyWS_COMMENT(s):
46 assert s.eqparse(s.s2.replace('\n',' #COMMENT \n')) == s.eqparse(s.s2.replace('\n','#\n')) == s.eqparse(s.s1)
49 assert s.eqparse('') == s.eqparse('\n') == s.eqparse('') == s.eqparse('a{}') == \
50 s.eqparse('a.b.c{if ""==\'\' {d.e{\n\n#Nothing\n}} }') == []
52 def test_syntax_errors(s):
53 s.assertRaises(ConfigSyntaxError, s.parse, "a=#")
54 s.assertRaises(ConfigSyntaxError, s.parse, "a='\"")
55 s.assertRaises(ConfigSyntaxError, s.parse, 'a="{a@b}"')
56 s.assertRaises(ConfigSyntaxError, s.parse, 'a="A{A"')
57 s.assertRaises(ConfigSyntaxError, s.parse, 'a=b"42"')
58 s.assertRaises(ConfigSyntaxError, s.parse, 'a=b.c.d.')
60 def test_error_location(s):
61 try: s.parse('\t \n \n { \n \n ')
62 except ConfigSyntaxError, e:
63 assert e.line == 3 and e.column in range(2,4)
66 s.parse(' a="\\"\\{a$b\\}\'\n\n\'{z}" ')
67 assert s.var('z', create=False)
68 # No escaping in '-string
69 s.assertRaises(ConfigSyntaxError, s.parse, " a='\"\\'\n\n' ")
70 # Variable should not be created
72 s.assertRaises(cf.ConfigError, s.var, 'z2', create=False)
74 def test_conditions(s):
75 s.assertRaises(ConfigSyntaxError, s.parse, "if '{a}'=='{b}' and ''!='' {}")
76 s.parse('if ((#C\n (\n (not not not""!="")\n#C\n)\t ) ) {}')
77 s.parse('if (""=="" and not (not not ""!="" or ""=="")){}')
78 s.parse('if ((a=="{b}" and a.b.c!=c.b.a)or x!=y){}')
79 s.parse('if(""==""){a{if(""==""){if(""==""){b{if(""==""){if(""==""){}}}}}}}')
80 s.assertRaises(ConfigSyntaxError, s.parse, "if notnot'{a}'=='{b}' {}")
81 s.assertRaises(ConfigSyntaxError, s.parse, "if ('{a}'=='{b}' not and ''!='') {}")
82 s.assertRaises(ConfigSyntaxError, s.parse, "if ('{a}'=='{b}' ornot ''!='') {}")
83 s.assertRaises(ConfigSyntaxError, s.parse, "if 'a'<>'b' {}")
86 class TestConfigEval(TestConfig):
89 s.parse('c+="-C_APP"', level=20)
90 s.parse('a="A"; b="{a}-B"; c="C1-{b}-C2"; a+="FOO"; a="AA"')
91 assert s.val('c') == 'C1-AA-B-C2-C_APP'
92 s.parse('b+="-A:\{{a}\}";a+="A"', level=10)
93 assert s.val('c') == 'C1-AAA-B-A:{AAA}-C2-C_APP'
96 s.parse('a="0"; b{a="1"; b{a="2"; b{a="3"; b{a="4"; b{a="5"}}}}}')
97 assert s.val('b.b.b.a') == '3'
98 s.parse('b.b{b.b{b.a="5MOD"}}')
99 assert s.val('b.b.b.b.b.a') == '5MOD'
101 def test_escape_chars(s):
102 s.parse(r"""a='{a}\\\\#\n'; b="{a}'\"\{\}"; c='\'; c+="\{{b}\}";""")
103 assert s.val('c') == r"""\{{a}\\\\#\n'"{}}"""
105 def test_expressions(s):
106 s.parse('A="4"; B.B=\'2\'; B.A=A; B.C="{B.A}{B.B}"; if B.C=="42" {D=C}; if C==D{E=D}; C="OK"')
107 assert s.val('E') == 'OK'
109 ts = 'a="A:"; if "{c1}"=="1" {a+="C1"; b="B"; if ("{c2a}"=="1" or not "{c2b}"=="1") { a+="C2"; '\
110 'if ("{c3a}"=="1" and "{c3b}"=="1") { a+="C3" }}}'
112 def test_cond_chain(s):
114 s.parse('c1="1"; c2="0"')
115 # b should have determined value, a should not (since c3a is undefined)
116 s.assertRaises(cf.UndefinedError, s.val, 'a')
117 assert s.val('b') == 'B'
119 # now b should be undefined
120 s.assertRaises(cf.UndefinedError, s.val, 'b')
122 s.parse('c1="1"; c2a="1"; c2b="0"; c3a="0"')
123 assert s.val('a') == 'A:C1C2'
124 s.parse('c3a="1"; c3b="1"; c2b="1"')
125 assert s.val('a') == 'A:C1C2C3'
126 # tests condition invalidating
128 assert s.val('a') == 'A:C1'
130 def test_cond_eager(s):
132 # undefined c2b and c3a should not be evaluated
133 s.parse('c1="1"; c2a="1"; c3a="0"')
134 assert s.val('a') == 'A:C1C2'
135 # but now c3b should be evaluated
136 s.parse('c1="1"; c2a="1"; c3a="1"')
137 s.assertRaises(cf.UndefinedError, s.val, 'a')
138 s.parse('c1="1"; c2a="1"; c3b="1"')
139 assert s.val('a') == 'A:C1C2C3'
142 s.assertRaises(cf.UndefinedError, s.val, 'a')
144 s.assertRaises(cf.UndefinedError, s.val, 'a')
146 s.assertRaises(cf.UndefinedError, s.val, 'b')
148 def test_loopy_def(s):
149 s.parse('a="A"; a+="{a}"')
150 s.assertRaises(cf.CyclicConfigError, s.val, 'a')
151 s.parse('b="{c}"; c="{b}"')
152 s.assertRaises(cf.CyclicConfigError, s.val, 'b')
155 s.assertRaises(cf.VariableNameError, s.val, 'b/c')
156 s.assertRaises(cf.VariableNameError, s.val, '.b.c')
157 s.assertRaises(cf.VariableNameError, s.val, 'b.c.')
158 s.assertRaises(cf.VariableNameError, s.val, 'b..c')
161 l = s.parse('a="A1"; b="B1"; if "{cond}"=="1" {a+="A2"; b+="B2"}; a+="A3"; b+="B3"; cond="1"')
162 assert s.val('a') == 'A1A2A3'
163 assert s.val('b') == 'B1B2B3'
165 s.var('b').remove_operation(l[3][1])
166 assert s.val('a') == 'A1A2A3'
167 assert s.val('b') == 'B1B3'
168 # are the dependencies still handled properly?
169 s.parse('cond+="-invalidated"')
170 assert s.val('a') == 'A1A3'
172 assert s.val('a') == 'A1A2A3'
175 s.parse(""" A='4'; B="{A}"; C="2"; B+="{C}"; D="{B}"; E="{D}" """)
179 s.assertRaises(cf.VariableFixedError, s.val, "E")
180 s.assertRaises(cf.VariableFixedError, s.val, "D")
181 s.var('C').remove_operation(l[0][1])
182 # Break directly by D
183 l = s.parse('D="41"')
184 s.assertRaises(cf.VariableFixedError, s.val, "D")
185 s.assertRaises(cf.VariableFixedError, s.val, "E")
188 assert s.val('E') == '41'
189 s.var('D').remove_operation(l[0][1])
190 assert s.val('D') == '42'
191 # Fixing via ConfigTree.fix
194 s.parse('D=""; E=""; A=""; ')
195 s.assertRaises(cf.VariableFixedError, s.val, "D")
196 s.assertRaises(cf.VariableFixedError, s.val, "E")
197 s.assertRaises(cf.VariableFixedError, s.val, "A")
200 # Ascii (1b) and unicode (2b)
201 s.parse(u'A="Ú"; C="ě"')
203 s.parse('B=\'chyln\'')
205 s.parse(u'D=\'\u0159e\u017eav\xe1\'')
208 f = tempfile.TemporaryFile(mode='w+t')
209 f.write(u'S1="\xdachyln\u011b \u0159e\u017eav\xe1 \u017dlu\u0164" ; S2="{A}{B}{C} {D} {E}"'.encode('utf8'))
213 s.parse(u'if "{S1}"=="{S2}" { ANS="jó!" } ')
214 assert s.val('ANS') == u"jó!"
215 assert s.val('S1') == s.val('S2') == u'\xdachyln\u011b \u0159e\u017eav\xe1 \u017dlu\u0164'
217 def test_priority(s):
218 s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["4"]), level=4))
219 s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["3a"]), level=3))
220 s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["1"]), level=1))
221 s.var('a').add_operation(cf.Operation('SET', None, cf.ConfigExpression(["2"]), level=2))
222 s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["3b"]), level=3))
223 s.var('a').add_operation(cf.Operation('SET', None, cf.ConfigExpression(["0"]), level=0))
224 s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["5"]), level=5))
225 assert s.val('a')=='23a3b45'
229 # TODO: Fail on 1st April
230 # TODO (OPT): Somehow add log.debug('Maximum encountered depth: %d', cf.debug_maxdepth)
232 # Coverage via command "nosetests conftest --with-coverage --cover-html-dir=cover --cover-html"