bash_completion: Enable extglob while parsing file
[ganeti-local] / test / ganeti.qlang_unittest.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010, 2011 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Script for testing ganeti.qlang"""
23
24 import unittest
25 import string
26
27 from ganeti import utils
28 from ganeti import errors
29 from ganeti import qlang
30 from ganeti import query
31
32 import testutils
33
34
35 class TestMakeSimpleFilter(unittest.TestCase):
36   def _Test(self, field, names, expected, parse_exp=None):
37     if parse_exp is None:
38       parse_exp = names
39
40     qfilter = qlang.MakeSimpleFilter(field, names)
41     self.assertEqual(qfilter, expected)
42
43   def test(self):
44     self._Test("name", None, None, parse_exp=[])
45     self._Test("name", [], None)
46     self._Test("name", ["node1.example.com"],
47                ["|", ["=", "name", "node1.example.com"]])
48     self._Test("xyz", ["a", "b", "c"],
49                ["|", ["=", "xyz", "a"], ["=", "xyz", "b"], ["=", "xyz", "c"]])
50
51
52 class TestParseFilter(unittest.TestCase):
53   def setUp(self):
54     self.parser = qlang.BuildFilterParser()
55
56   def _Test(self, qfilter, expected, expect_filter=True):
57     self.assertEqual(qlang.MakeFilter([qfilter], not expect_filter), expected)
58     self.assertEqual(qlang.ParseFilter(qfilter, parser=self.parser), expected)
59
60   def test(self):
61     self._Test("name==\"foobar\"", [qlang.OP_EQUAL, "name", "foobar"])
62     self._Test("name=='foobar'", [qlang.OP_EQUAL, "name", "foobar"])
63
64     self._Test("valA==1 and valB==2 or valC==3",
65                [qlang.OP_OR,
66                 [qlang.OP_AND, [qlang.OP_EQUAL, "valA", 1],
67                                [qlang.OP_EQUAL, "valB", 2]],
68                 [qlang.OP_EQUAL, "valC", 3]])
69
70     self._Test(("(name\n==\"foobar\") and (xyz==\"va)ue\" and k == 256 or"
71                 " x ==\t\"y\"\n) and mc"),
72                [qlang.OP_AND,
73                 [qlang.OP_EQUAL, "name", "foobar"],
74                 [qlang.OP_OR,
75                  [qlang.OP_AND, [qlang.OP_EQUAL, "xyz", "va)ue"],
76                                 [qlang.OP_EQUAL, "k", 256]],
77                  [qlang.OP_EQUAL, "x", "y"]],
78                 [qlang.OP_TRUE, "mc"]])
79
80     self._Test("(xyz==\"v\" or k == 256 and x == \"y\")",
81                [qlang.OP_OR,
82                 [qlang.OP_EQUAL, "xyz", "v"],
83                 [qlang.OP_AND, [qlang.OP_EQUAL, "k", 256],
84                                [qlang.OP_EQUAL, "x", "y"]]])
85
86     self._Test("valA==1 and valB==2 and valC==3",
87                [qlang.OP_AND, [qlang.OP_EQUAL, "valA", 1],
88                               [qlang.OP_EQUAL, "valB", 2],
89                               [qlang.OP_EQUAL, "valC", 3]])
90     self._Test("master or field",
91                [qlang.OP_OR, [qlang.OP_TRUE, "master"],
92                              [qlang.OP_TRUE, "field"]])
93     self._Test("mem == 128", [qlang.OP_EQUAL, "mem", 128])
94     self._Test("negfield != -1", [qlang.OP_NOT_EQUAL, "negfield", -1])
95     self._Test("master", [qlang.OP_TRUE, "master"],
96                expect_filter=False)
97     self._Test("not master", [qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
98     for op in ["not", "and", "or"]:
99       self._Test("%sxyz" % op, [qlang.OP_TRUE, "%sxyz" % op],
100                  expect_filter=False)
101       self._Test("not %sxyz" % op,
102                  [qlang.OP_NOT, [qlang.OP_TRUE, "%sxyz" % op]])
103       self._Test("  not \t%sfoo" % op,
104                  [qlang.OP_NOT, [qlang.OP_TRUE, "%sfoo" % op]])
105       self._Test("%sname =~ m/abc/" % op,
106                  [qlang.OP_REGEXP, "%sname" % op, "abc"])
107     self._Test("master and not other",
108                [qlang.OP_AND, [qlang.OP_TRUE, "master"],
109                               [qlang.OP_NOT, [qlang.OP_TRUE, "other"]]])
110     self._Test("not (master or other == 4)",
111                [qlang.OP_NOT,
112                 [qlang.OP_OR, [qlang.OP_TRUE, "master"],
113                               [qlang.OP_EQUAL, "other", 4]]])
114     self._Test("some==\"val\\\"ue\"", [qlang.OP_EQUAL, "some", "val\\\"ue"])
115     self._Test("123 in ips", [qlang.OP_CONTAINS, "ips", 123])
116     self._Test("99 not in ips", [qlang.OP_NOT, [qlang.OP_CONTAINS, "ips", 99]])
117     self._Test("\"a\" in valA and \"b\" not in valB",
118                [qlang.OP_AND, [qlang.OP_CONTAINS, "valA", "a"],
119                               [qlang.OP_NOT, [qlang.OP_CONTAINS, "valB", "b"]]])
120
121     self._Test("name =~ m/test/", [qlang.OP_REGEXP, "name", "test"])
122     self._Test("name =~ m/^node.*example.com$/i",
123                [qlang.OP_REGEXP, "name", "(?i)^node.*example.com$"])
124     self._Test("(name =~ m/^node.*example.com$/s and master) or pip =~ |^3.*|",
125                [qlang.OP_OR,
126                 [qlang.OP_AND,
127                  [qlang.OP_REGEXP, "name", "(?s)^node.*example.com$"],
128                  [qlang.OP_TRUE, "master"]],
129                 [qlang.OP_REGEXP, "pip", "^3.*"]])
130     for flags in ["si", "is", "ssss", "iiiisiii"]:
131       self._Test("name =~ m/gi/%s" % flags,
132                  [qlang.OP_REGEXP, "name", "(?%s)gi" % "".join(sorted(flags))])
133
134     for i in qlang._KNOWN_REGEXP_DELIM:
135       self._Test("name =~ m%stest%s" % (i, i),
136                  [qlang.OP_REGEXP, "name", "test"])
137       self._Test("name !~ m%stest%s" % (i, i),
138                  [qlang.OP_NOT, [qlang.OP_REGEXP, "name", "test"]])
139       self._Test("not\tname =~ m%stest%s" % (i, i),
140                  [qlang.OP_NOT, [qlang.OP_REGEXP, "name", "test"]])
141       self._Test("notname =~ m%stest%s" % (i, i),
142                  [qlang.OP_REGEXP, "notname", "test"])
143
144     self._Test("name =* '*.site'",
145                [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("*.site")])
146     self._Test("field !* '*.example.*'",
147                [qlang.OP_NOT, [qlang.OP_REGEXP, "field",
148                                utils.DnsNameGlobPattern("*.example.*")]])
149
150     self._Test("ctime < 1234", [qlang.OP_LT, "ctime", 1234])
151     self._Test("ctime > 1234", [qlang.OP_GT, "ctime", 1234])
152     self._Test("mtime <= 9999", [qlang.OP_LE, "mtime", 9999])
153     self._Test("mtime >= 9999", [qlang.OP_GE, "mtime", 9999])
154
155   def testAllFields(self):
156     for name in frozenset(i for d in query.ALL_FIELD_LISTS for i in d.keys()):
157       self._Test("%s == \"value\"" % name, [qlang.OP_EQUAL, name, "value"])
158
159   def testError(self):
160     # Invalid field names, meaning no boolean check is done
161     tests = ["#invalid!filter#", "m/x/,"]
162
163     # Unknown regexp flag
164     tests.append("name=~m#a#g")
165
166     # Incomplete regexp group
167     tests.append("name=~^[^")
168
169     # Valid flag, but in uppercase
170     tests.append("asdf =~ m|abc|I")
171
172     # Non-matching regexp delimiters
173     tests.append("name =~ /foobarbaz#")
174
175     # Invalid operators
176     tests.append("name <> value")
177     tests.append("name => value")
178     tests.append("name =< value")
179
180     for qfilter in tests:
181       try:
182         qlang.ParseFilter(qfilter, parser=self.parser)
183       except errors.QueryFilterParseError, err:
184         self.assertEqual(len(err.GetDetails()), 3)
185       else:
186         self.fail("Invalid filter '%s' did not raise exception" % qfilter)
187
188
189 class TestMakeFilter(unittest.TestCase):
190   def testNoNames(self):
191     self.assertEqual(qlang.MakeFilter([], False), None)
192     self.assertEqual(qlang.MakeFilter(None, False), None)
193
194   def testPlainNames(self):
195     self.assertEqual(qlang.MakeFilter(["web1", "web2"], False),
196                      [qlang.OP_OR, [qlang.OP_EQUAL, "name", "web1"],
197                                    [qlang.OP_EQUAL, "name", "web2"]])
198
199   def testPlainNamesOtherNamefield(self):
200     self.assertEqual(qlang.MakeFilter(["mailA", "mailB"], False,
201                                       namefield="id"),
202                      [qlang.OP_OR, [qlang.OP_EQUAL, "id", "mailA"],
203                                    [qlang.OP_EQUAL, "id", "mailB"]])
204
205   def testForcedFilter(self):
206     for i in [None, [], ["1", "2"], ["", "", ""], ["a", "b", "c", "d"]]:
207       self.assertRaises(errors.OpPrereqError, qlang.MakeFilter, i, True)
208
209     # Glob pattern shouldn't parse as filter
210     self.assertRaises(errors.QueryFilterParseError,
211                       qlang.MakeFilter, ["*.site"], True)
212
213     # Plain name parses as boolean filter
214     self.assertEqual(qlang.MakeFilter(["web1"], True), [qlang.OP_TRUE, "web1"])
215
216   def testFilter(self):
217     self.assertEqual(qlang.MakeFilter(["foo/bar"], False),
218                      [qlang.OP_TRUE, "foo/bar"])
219     self.assertEqual(qlang.MakeFilter(["foo=='bar'"], False),
220                      [qlang.OP_EQUAL, "foo", "bar"])
221     self.assertEqual(qlang.MakeFilter(["field=*'*.site'"], False),
222                      [qlang.OP_REGEXP, "field",
223                       utils.DnsNameGlobPattern("*.site")])
224
225     # Plain name parses as name filter, not boolean
226     for name in ["node1", "n-o-d-e", "n_o_d_e", "node1.example.com",
227                  "node1.example.com."]:
228       self.assertEqual(qlang.MakeFilter([name], False),
229                        [qlang.OP_OR, [qlang.OP_EQUAL, "name", name]])
230
231     # Invalid filters
232     for i in ["foo==bar", "foo+=1"]:
233       self.assertRaises(errors.QueryFilterParseError,
234                         qlang.MakeFilter, [i], False)
235
236   def testGlob(self):
237     self.assertEqual(qlang.MakeFilter(["*.site"], False),
238                      [qlang.OP_OR, [qlang.OP_REGEXP, "name",
239                                     utils.DnsNameGlobPattern("*.site")]])
240     self.assertEqual(qlang.MakeFilter(["web?.example"], False),
241                      [qlang.OP_OR, [qlang.OP_REGEXP, "name",
242                                     utils.DnsNameGlobPattern("web?.example")]])
243     self.assertEqual(qlang.MakeFilter(["*.a", "*.b", "?.c"], False),
244                      [qlang.OP_OR,
245                       [qlang.OP_REGEXP, "name",
246                        utils.DnsNameGlobPattern("*.a")],
247                       [qlang.OP_REGEXP, "name",
248                        utils.DnsNameGlobPattern("*.b")],
249                       [qlang.OP_REGEXP, "name",
250                        utils.DnsNameGlobPattern("?.c")]])
251
252
253 if __name__ == "__main__":
254   testutils.GanetiTestProgram()