]> mj.ucw.cz Git - moe.git/blob - t/moe/config_test.py
Merge branch 'python-newpipe' into python
[moe.git] / t / moe / config_test.py
1 # -*- coding: utf-8 -*-
2
3 import moe.config as cf
4 from moe.config_parser import *
5 import unittest
6 import tempfile
7
8 class TestConfig(unittest.TestCase):
9
10   def setUp(s):
11     s.t = cf.ConfigTree()    
12
13   def parse(s, string, level=0, fname='test'):
14     return s.t.parse(string, source=fname, level=level)
15
16   def var(s, varname, create=True):
17     return s.t.lookup(varname, create=create)
18
19   def val(s, varname):
20     return s.var(varname, create=False).value()
21
22   def eqparse(s, string, *args, **kwargs):
23     "Parse expression `s`, return parts that should be equal to anoter run of `eqparse(s)`."
24     return [(i[0], i[1].operation) for i in s.parse(string, *args, **kwargs)]
25
26
27 class TestParser(TestConfig):
28   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"""
29   s2 = '\t\n \n ' + s1.replace('=', '= \n ').replace(';', '\t \n\t \n ').replace('+=',' \n += ') + '\n\n '
30
31   def test_noWS(s):
32     assert len(s.parse(s.s1)) == 11
33
34   def test_noWS_COMMENT(s):
35     assert s.eqparse(s.s1+'#COMMENT') == s.eqparse(s.s1+'#') == s.eqparse(s.s1+'#\n') == s.eqparse(s.s1+'\n#')
36
37   def test_manyWS(s):
38     assert s.eqparse(s.s2) == s.eqparse(s.s1)
39
40   def test_manyWS_COMMENT(s):
41     assert s.eqparse(s.s2.replace('\n',' #COMMENT \n')) == s.eqparse(s.s2.replace('\n','#\n')) == s.eqparse(s.s1)
42
43   def test_empty(s):
44     assert s.eqparse('') == s.eqparse('\n') == s.eqparse('') == s.eqparse('a{}') == \
45       s.eqparse('a.b.c{if ""==\'\' {d.e{\n\n#Nothing\n}} }') == []
46
47   def test_syntax_errors(s):
48     s.assertRaises(ConfigSyntaxError, s.parse, "a=#")
49     s.assertRaises(ConfigSyntaxError, s.parse, "a='\"")
50     s.assertRaises(ConfigSyntaxError, s.parse, 'a="{a@b}"')
51     s.assertRaises(ConfigSyntaxError, s.parse, 'a="A{A"')
52     s.assertRaises(ConfigSyntaxError, s.parse, 'a=b"42"')
53     s.assertRaises(ConfigSyntaxError, s.parse, 'a=b.c.d.')
54
55   def test_error_location(s):
56     try: s.parse('\t \n  \n  { \n \n ')
57     except ConfigSyntaxError, e:
58       assert e.line == 3 and e.column in range(2,4)
59
60   def test_quoting(s):
61     s.parse(' a="\\"\\{a$b\\}\'\n\n\'{z}" ')
62     assert s.var('z', create=False)
63     # No escaping in '-string 
64     s.assertRaises(ConfigSyntaxError, s.parse, " a='\"\\'\n\n' ")
65     # Variable should not be created
66     s.parse(" a='{z2}' ")
67     s.assertRaises(cf.ConfigError, s.var, 'z2', create=False)
68
69   def test_conditions(s):
70     s.assertRaises(ConfigSyntaxError, s.parse, "if '{a}'=='{b}' and ''!='' {}")
71     s.parse('if ((#C\n (\n (not not not""!="")\n#C\n)\t ) ) {}')
72     s.parse('if (""=="" and not (not not ""!="" or ""=="")){}')
73     s.parse('if ((a=="{b}" and a.b.c!=c.b.a)or x!=y){}')
74     s.parse('if(""==""){a{if(""==""){if(""==""){b{if(""==""){if(""==""){}}}}}}}')
75     s.assertRaises(ConfigSyntaxError, s.parse, "if notnot'{a}'=='{b}' {}")
76     s.assertRaises(ConfigSyntaxError, s.parse, "if ('{a}'=='{b}' not and ''!='') {}")
77     s.assertRaises(ConfigSyntaxError, s.parse, "if ('{a}'=='{b}' ornot ''!='') {}")
78     s.assertRaises(ConfigSyntaxError, s.parse, "if 'a'<>'b' {}")
79
80   def test_parse_remove(s):
81     def raise_UserWarning():
82       with s.parse("b='F'", level=99):
83         assert s.val('b') == 'F'
84         raise UserWarning 
85     d0 = s.parse('a="000"')
86     d1 = s.parse("a='A'; b='B'; c='C'", level=10)
87     d2 = s.parse('c="{a}{a}"; b="XX" ', level=20)
88     d3 = s.parse('b+=c ', level=30)
89     assert s.val('b') == "XXAA"
90     s.t.remove(d2)
91     assert s.val('b') == "BC"
92     s.assertRaises(ValueError, s.t.remove, [('d', d1[0][1])])
93     s.assertRaises(ValueError, s.t.remove, [('b', d1[0][1])])
94     # Try exception in "with parse():" 
95     s.assertRaises(UserWarning, raise_UserWarning)
96     assert s.val('b') == "BC"
97     # partially remove d1
98     s.t.remove([('c', d1[2][1])])
99     # try to remove rest - 'a' and 'b' should get removed
100     s.assertRaises(ValueError, s.t.remove, d1)
101     assert s.val('a') == "000"
102     # cleanup
103     s.t.remove(d3)
104     s.t.remove(d0)
105     for v in 'abcd':
106       assert len(s.var(v).operations) == 0
107
108   def test_escape(s):
109     for tst in ['{B}"#\n\\"', '', '\\\n\\\\"\\{\\}', '"#"', s.s1, s.s2]:
110       s.parse('A="%s"; A+="0"'%config_escape(tst))
111       assert s.val('A') == tst+'0'
112
113 class TestConfigEval(TestConfig):
114
115   def test_ops(s):
116     s.parse('c+="-C_APP"', level=20)
117     s.parse('a="A"; b="{a}-B"; c="C1-{b}-C2"; a+="FOO"; a="AA"')
118     assert s.val('c') == 'C1-AA-B-C2-C_APP'
119     s.parse('b+="-A:\{{a}\}";a+="A"', level=10)
120     assert s.val('c') == 'C1-AAA-B-A:{AAA}-C2-C_APP'
121
122   def test_nested(s):
123     s.parse('a="0"; b{a="1"; b{a="2"; b{a="3"; b{a="4"; b{a="5"}}}}}')
124     assert s.val('b.b.b.a') == '3'
125     s.parse('b.b{b.b{b.a="5MOD"}}')
126     assert s.val('b.b.b.b.b.a') == '5MOD'
127
128   def test_escape_chars(s):
129     s.parse(r"""a='{a}\\\\#\n'; b="{a}'\"\{\}"; c='\'; c+="\{{b}\}";""")
130     assert s.val('c') == r"""\{{a}\\\\#\n'"{}}"""
131
132   def test_expressions(s):
133     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"')
134     assert s.val('E') == 'OK'
135   
136   ts = 'a="A:"; if "{c1}"=="1" {a+="C1"; b="B"; if ("{c2a}"=="1" or not "{c2b}"=="1") { a+="C2"; '\
137     'if ("{c3a}"=="1" and "{c3b}"=="1") { a+="C3" }}}'
138
139   def test_cond_chain(s):
140     s.parse(s.ts)
141     s.parse('c1="1"; c2="0"')
142     # b should have determined value, a should not (since c3a is undefined)
143     s.assertRaises(cf.UndefinedError, s.val, 'a')
144     assert s.val('b') == 'B'
145     s.parse('c1="0"')
146     # now b should be undefined
147     s.assertRaises(cf.UndefinedError, s.val, 'b')
148     # Normal evaluation
149     s.parse('c1="1"; c2a="1"; c2b="0"; c3a="0"')
150     assert s.val('a') == 'A:C1C2'
151     s.parse('c3a="1"; c3b="1"; c2b="1"')
152     assert s.val('a') == 'A:C1C2C3'
153     # tests condition invalidating 
154     s.parse('c2a+="0"')
155     assert s.val('a') == 'A:C1'
156
157   def test_cond_eager(s):
158     s.parse(s.ts)
159     # undefined c2b and c3a should not be evaluated 
160     s.parse('c1="1"; c2a="1"; c3a="0"')
161     assert s.val('a') == 'A:C1C2'
162     # but now c3b should be evaluated 
163     s.parse('c1="1"; c2a="1"; c3a="1"')
164     s.assertRaises(cf.UndefinedError, s.val, 'a')
165     s.parse('c1="1"; c2a="1"; c3b="1"')
166     assert s.val('a') == 'A:C1C2C3'
167
168   def test_undef(s):
169     s.assertRaises(cf.UndefinedError, s.val, 'a')
170     s.parse('a="{b}"')
171     s.assertRaises(cf.UndefinedError, s.val, 'a')
172     s.parse('b+="1"')
173     s.assertRaises(cf.UndefinedError, s.val, 'b')
174
175   def test_loopy_def(s):
176     s.parse('a="A"; a+="{a}"')
177     s.assertRaises(cf.CyclicConfigError, s.val, 'a')
178     s.parse('b="{c}"; c="{b}"')
179     s.assertRaises(cf.CyclicConfigError, s.val, 'b')
180
181   def test_varname(s):
182     s.assertRaises(cf.VariableNameError, s.val, 'b/c')
183     s.assertRaises(cf.VariableNameError, s.val, '.b.c')
184     s.assertRaises(cf.VariableNameError, s.val, 'b.c.')
185     s.assertRaises(cf.VariableNameError, s.val, 'b..c')
186
187   def test_remove(s):
188     l = s.parse('a="A1"; b="B1"; if "{cond}"=="1" {a+="A2"; b+="B2"}; a+="A3"; b+="B3"; cond="1"')
189     assert s.val('a') == 'A1A2A3'
190     assert s.val('b') == 'B1B2B3'
191     # remove b+="B2"
192     s.var('b').remove_operation(l[3][1])
193     assert s.val('a') == 'A1A2A3'
194     assert s.val('b') == 'B1B3'
195     # are the dependencies still handled properly? 
196     s.parse('cond+="-invalidated"')
197     assert s.val('a') == 'A1A3'
198     s.parse('cond="1"')
199     assert s.val('a') == 'A1A2A3'
200
201   def test_fix(s):
202     s.parse(""" A='4'; B="{A}"; C="2"; B+="{C}"; D="{B}"; E="{D}" """)
203     s.var('D').fix()
204     # Break by C 
205     l = s.parse('C="3"')
206     s.assertRaises(cf.VariableFixedError, s.val, "E")
207     s.assertRaises(cf.VariableFixedError, s.val, "D")
208     s.var('C').remove_operation(l[0][1])
209     # Break directly by D 
210     l = s.parse('D="41"')
211     s.assertRaises(cf.VariableFixedError, s.val, "D")
212     s.assertRaises(cf.VariableFixedError, s.val, "E")
213     # Unfix
214     s.var('D').unfix()
215     assert s.val('E') == '41'
216     s.var('D').remove_operation(l[0][1])
217     assert s.val('D') == '42'
218     # Fixing via ConfigTree.fix
219     s.t.fix('D')
220     s.t.fix(['E','A'])
221     s.parse('D=""; E=""; A=""; ')
222     s.assertRaises(cf.VariableFixedError, s.val, "D")
223     s.assertRaises(cf.VariableFixedError, s.val, "E")
224     s.assertRaises(cf.VariableFixedError, s.val, "A")
225
226   def test_unicode(s):
227     # Ascii (1b) and unicode (2b)
228     s.parse(u'A="Ú"; C="ě"')
229     # String
230     s.parse('B=\'chyln\'')
231     # By escapes  
232     s.parse(u'D=\'\u0159e\u017eav\xe1\'')
233     s.parse(u'E="ŽluŤ"')
234     # Via utf8 file
235     f = tempfile.TemporaryFile(mode='w+t')
236     f.write(u'S1="\xdachyln\u011b \u0159e\u017eav\xe1 \u017dlu\u0164" ; S2="{A}{B}{C} {D} {E}"'.encode('utf8'))
237     f.seek(0)
238     s.parse(f)
239     # Test
240     s.parse(u'if "{S1}"=="{S2}" { ANS="jó!" } ')
241     assert s.val('ANS') == u"jó!"
242     assert s.val('S1') == s.val('S2') == u'\xdachyln\u011b \u0159e\u017eav\xe1 \u017dlu\u0164'
243
244   def test_priority(s):
245     s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["4"]), level=4))
246     s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["3a"]), level=3))
247     s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["1"]), level=1))
248     s.var('a').add_operation(cf.Operation('SET', None, cf.ConfigExpression(["2"]), level=2))
249     s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["3b"]), level=3))
250     s.var('a').add_operation(cf.Operation('SET', None, cf.ConfigExpression(["0"]), level=0))
251     s.var('a').add_operation(cf.Operation('APPEND', None, cf.ConfigExpression(["5"]), level=5))
252     assert s.val('a')=='23a3b45'
253
254   def test_priority_in_level(s):
255     s.parse('a="A"; c=""; b="B"; c+="C"; d="D"', level=0)
256     s.parse('a=b; b=c; c=d; d="ZZZ"', level=10)
257     s.parse('c="XXX"; c=""; d="S"; c+="YYY"', level=20)
258     assert s.val('a') == "YYY"
259
260 # TODO: Fail on 1st April
261 # TODO (OPT): Somehow add log.debug('Maximum encountered depth: %d', cf.debug_maxdepth)
262
263 # Coverage via command "nosetests conftest --with-coverage --cover-html-dir=cover --cover-html"
264