Coverage for tests/test_exprParserYacc.py : 12%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""Simple unit test for exprParser subpackage module.
23"""
25import unittest
27from lsst.daf.butler.registry.queries.exprParser import exprTree, TreeVisitor, ParserYacc, ParseError
30class _Visitor(TreeVisitor):
31 """Trivial implementation of TreeVisitor.
32 """
33 def visitNumericLiteral(self, value, node):
34 return f"N({value})"
36 def visitStringLiteral(self, value, node):
37 return f"S({value})"
39 def visitRangeLiteral(self, start, stop, stride, node):
40 if stride is None:
41 return f"R({start}..{stop})"
42 else:
43 return f"R({start}..{stop}:{stride})"
45 def visitIdentifier(self, name, node):
46 return f"ID({name})"
48 def visitUnaryOp(self, operator, operand, node):
49 return f"U({operator} {operand})"
51 def visitBinaryOp(self, operator, lhs, rhs, node):
52 return f"B({lhs} {operator} {rhs})"
54 def visitIsIn(self, lhs, values, not_in, node):
55 values = ", ".join([str(val) for val in values])
56 if not_in:
57 return f"!IN({lhs} ({values}))"
58 else:
59 return f"IN({lhs} ({values}))"
61 def visitParens(self, expression, node):
62 return f"P({expression})"
65class ParserLexTestCase(unittest.TestCase):
66 """A test case for ParserYacc
67 """
69 def setUp(self):
70 pass
72 def tearDown(self):
73 pass
75 def testInstantiate(self):
76 """Tests for making ParserLex instances
77 """
78 parser = ParserYacc() # noqa: F841
80 def testEmpty(self):
81 """Tests for empty expression
82 """
83 parser = ParserYacc()
85 # empty expression is allowed, returns None
86 tree = parser.parse("")
87 self.assertIsNone(tree)
89 def testParseLiteral(self):
90 """Tests for literals (strings/numbers)
91 """
92 parser = ParserYacc()
94 tree = parser.parse('1')
95 self.assertIsInstance(tree, exprTree.NumericLiteral)
96 self.assertEqual(tree.value, '1')
98 tree = parser.parse('.5e-2')
99 self.assertIsInstance(tree, exprTree.NumericLiteral)
100 self.assertEqual(tree.value, '.5e-2')
102 tree = parser.parse("'string'")
103 self.assertIsInstance(tree, exprTree.StringLiteral)
104 self.assertEqual(tree.value, 'string')
106 tree = parser.parse("10..20")
107 self.assertIsInstance(tree, exprTree.RangeLiteral)
108 self.assertEqual(tree.start, 10)
109 self.assertEqual(tree.stop, 20)
110 self.assertEqual(tree.stride, None)
112 tree = parser.parse("-10 .. 10:5")
113 self.assertIsInstance(tree, exprTree.RangeLiteral)
114 self.assertEqual(tree.start, -10)
115 self.assertEqual(tree.stop, 10)
116 self.assertEqual(tree.stride, 5)
118 def testParseIdentifiers(self):
119 """Tests for identifiers
120 """
121 parser = ParserYacc()
123 tree = parser.parse('a')
124 self.assertIsInstance(tree, exprTree.Identifier)
125 self.assertEqual(tree.name, 'a')
127 tree = parser.parse('a.b')
128 self.assertIsInstance(tree, exprTree.Identifier)
129 self.assertEqual(tree.name, 'a.b')
131 def testParseParens(self):
132 """Tests for identifiers
133 """
134 parser = ParserYacc()
136 tree = parser.parse('(a)')
137 self.assertIsInstance(tree, exprTree.Parens)
138 self.assertIsInstance(tree.expr, exprTree.Identifier)
139 self.assertEqual(tree.expr.name, 'a')
141 def testUnaryOps(self):
142 """Tests for unary plus and minus
143 """
144 parser = ParserYacc()
146 tree = parser.parse('+a')
147 self.assertIsInstance(tree, exprTree.UnaryOp)
148 self.assertEqual(tree.op, '+')
149 self.assertIsInstance(tree.operand, exprTree.Identifier)
150 self.assertEqual(tree.operand.name, 'a')
152 tree = parser.parse('- x.y')
153 self.assertIsInstance(tree, exprTree.UnaryOp)
154 self.assertEqual(tree.op, '-')
155 self.assertIsInstance(tree.operand, exprTree.Identifier)
156 self.assertEqual(tree.operand.name, 'x.y')
158 def testBinaryOps(self):
159 """Tests for binary operators
160 """
161 parser = ParserYacc()
163 tree = parser.parse('a + b')
164 self.assertIsInstance(tree, exprTree.BinaryOp)
165 self.assertEqual(tree.op, '+')
166 self.assertIsInstance(tree.lhs, exprTree.Identifier)
167 self.assertIsInstance(tree.rhs, exprTree.Identifier)
168 self.assertEqual(tree.lhs.name, 'a')
169 self.assertEqual(tree.rhs.name, 'b')
171 tree = parser.parse('a - 2')
172 self.assertIsInstance(tree, exprTree.BinaryOp)
173 self.assertEqual(tree.op, '-')
174 self.assertIsInstance(tree.lhs, exprTree.Identifier)
175 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
176 self.assertEqual(tree.lhs.name, 'a')
177 self.assertEqual(tree.rhs.value, '2')
179 tree = parser.parse('2 * 2')
180 self.assertIsInstance(tree, exprTree.BinaryOp)
181 self.assertEqual(tree.op, '*')
182 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
183 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
184 self.assertEqual(tree.lhs.value, '2')
185 self.assertEqual(tree.rhs.value, '2')
187 tree = parser.parse('1.e5/2')
188 self.assertIsInstance(tree, exprTree.BinaryOp)
189 self.assertEqual(tree.op, '/')
190 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
191 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
192 self.assertEqual(tree.lhs.value, '1.e5')
193 self.assertEqual(tree.rhs.value, '2')
195 tree = parser.parse('333%76')
196 self.assertIsInstance(tree, exprTree.BinaryOp)
197 self.assertEqual(tree.op, '%')
198 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
199 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
200 self.assertEqual(tree.lhs.value, '333')
201 self.assertEqual(tree.rhs.value, '76')
203 def testIsIn(self):
204 """Tests for IN
205 """
206 parser = ParserYacc()
208 tree = parser.parse("a in (1,2,'X')")
209 self.assertIsInstance(tree, exprTree.IsIn)
210 self.assertFalse(tree.not_in)
211 self.assertIsInstance(tree.lhs, exprTree.Identifier)
212 self.assertEqual(tree.lhs.name, 'a')
213 self.assertIsInstance(tree.values, list)
214 self.assertEqual(len(tree.values), 3)
215 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
216 self.assertEqual(tree.values[0].value, '1')
217 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral)
218 self.assertEqual(tree.values[1].value, '2')
219 self.assertIsInstance(tree.values[2], exprTree.StringLiteral)
220 self.assertEqual(tree.values[2].value, 'X')
222 tree = parser.parse("10 not in (1000, 2000..3000:100)")
223 self.assertIsInstance(tree, exprTree.IsIn)
224 self.assertTrue(tree.not_in)
225 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
226 self.assertEqual(tree.lhs.value, '10')
227 self.assertIsInstance(tree.values, list)
228 self.assertEqual(len(tree.values), 2)
229 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
230 self.assertEqual(tree.values[0].value, '1000')
231 self.assertIsInstance(tree.values[1], exprTree.RangeLiteral)
232 self.assertEqual(tree.values[1].start, 2000)
233 self.assertEqual(tree.values[1].stop, 3000)
234 self.assertEqual(tree.values[1].stride, 100)
236 tree = parser.parse("10 in (-1000, -2000)")
237 self.assertIsInstance(tree, exprTree.IsIn)
238 self.assertFalse(tree.not_in)
239 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
240 self.assertEqual(tree.lhs.value, '10')
241 self.assertIsInstance(tree.values, list)
242 self.assertEqual(len(tree.values), 2)
243 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
244 self.assertEqual(tree.values[0].value, '-1000')
245 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral)
246 self.assertEqual(tree.values[1].value, '-2000')
248 def testCompareOps(self):
249 """Tests for comparison operators
250 """
251 parser = ParserYacc()
253 for op in ('=', '!=', '<', '<=', '>', '>='):
254 tree = parser.parse('a {} 10'.format(op))
255 self.assertIsInstance(tree, exprTree.BinaryOp)
256 self.assertEqual(tree.op, op)
257 self.assertIsInstance(tree.lhs, exprTree.Identifier)
258 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
259 self.assertEqual(tree.lhs.name, 'a')
260 self.assertEqual(tree.rhs.value, '10')
262 def testBoolOps(self):
263 """Tests for boolean operators
264 """
265 parser = ParserYacc()
267 for op in ('OR', 'AND'):
268 tree = parser.parse('a {} b'.format(op))
269 self.assertIsInstance(tree, exprTree.BinaryOp)
270 self.assertEqual(tree.op, op)
271 self.assertIsInstance(tree.lhs, exprTree.Identifier)
272 self.assertIsInstance(tree.rhs, exprTree.Identifier)
273 self.assertEqual(tree.lhs.name, 'a')
274 self.assertEqual(tree.rhs.name, 'b')
276 tree = parser.parse('NOT b')
277 self.assertIsInstance(tree, exprTree.UnaryOp)
278 self.assertEqual(tree.op, 'NOT')
279 self.assertIsInstance(tree.operand, exprTree.Identifier)
280 self.assertEqual(tree.operand.name, 'b')
282 def testExpression(self):
283 """Test for more or less complete expression"""
284 parser = ParserYacc()
286 expression = ("((instrument='HSC' AND detector != 9) OR instrument='CFHT') "
287 "AND tract=8766 AND patch.cell_x > 5 AND "
288 "patch.cell_y < 4 AND abstract_filter='i'")
290 tree = parser.parse(expression)
291 self.assertIsInstance(tree, exprTree.BinaryOp)
292 self.assertEqual(tree.op, 'AND')
293 self.assertIsInstance(tree.lhs, exprTree.BinaryOp)
294 # AND is left-associative, so rhs operand will be the
295 # last sub-expressions
296 self.assertIsInstance(tree.rhs, exprTree.BinaryOp)
297 self.assertEqual(tree.rhs.op, '=')
298 self.assertIsInstance(tree.rhs.lhs, exprTree.Identifier)
299 self.assertEqual(tree.rhs.lhs.name, 'abstract_filter')
300 self.assertIsInstance(tree.rhs.rhs, exprTree.StringLiteral)
301 self.assertEqual(tree.rhs.rhs.value, 'i')
303 def testException(self):
304 """Test for exceptional cases"""
306 def _assertExc(exc, expr, token, pos, lineno, posInLine):
307 """Check exception attribute values"""
308 self.assertEqual(exc.expression, expr)
309 self.assertEqual(exc.token, token)
310 self.assertEqual(exc.pos, pos)
311 self.assertEqual(exc.lineno, lineno)
312 self.assertEqual(exc.posInLine, posInLine)
314 parser = ParserYacc()
316 expression = "(1, 2, 3)"
317 with self.assertRaises(ParseError) as catcher:
318 parser.parse(expression)
319 _assertExc(catcher.exception, expression, ",", 2, 1, 2)
321 expression = "\n(1\n,\n 2, 3)"
322 with self.assertRaises(ParseError) as catcher:
323 parser.parse(expression)
324 _assertExc(catcher.exception, expression, ",", 4, 3, 0)
326 def testStr(self):
327 """Test for formatting"""
328 parser = ParserYacc()
330 tree = parser.parse("(a+b)")
331 self.assertEqual(str(tree), '(a + b)')
333 tree = parser.parse("1 in (1,'x',3)")
334 self.assertEqual(str(tree), "1 IN (1, 'x', 3)")
336 tree = parser.parse("a not in (1,'x',3)")
337 self.assertEqual(str(tree), "a NOT IN (1, 'x', 3)")
339 tree = parser.parse("(A or B) And NoT (x+3 > y)")
340 self.assertEqual(str(tree), "(A OR B) AND NOT (x + 3 > y)")
342 tree = parser.parse("A in (100, 200..300:50)")
343 self.assertEqual(str(tree), "A IN (100, 200..300:50)")
345 def testVisit(self):
346 """Test for visitor methods"""
348 # test should cover all visit* methods
349 parser = ParserYacc()
350 visitor = _Visitor()
352 tree = parser.parse("(a+b)")
353 result = tree.visit(visitor)
354 self.assertEqual(result, "P(B(ID(a) + ID(b)))")
356 tree = parser.parse("(A or B) and not (x + 3 > y)")
357 result = tree.visit(visitor)
358 self.assertEqual(result, "B(P(B(ID(A) OR ID(B))) AND U(NOT P(B(B(ID(x) + N(3)) > ID(y)))))")
360 tree = parser.parse("x in (1,2) AND y NOT IN (1.1, .25, 1e2) OR z in ('a', 'b')")
361 result = tree.visit(visitor)
362 self.assertEqual(result, "B(B(IN(ID(x) (N(1), N(2))) AND !IN(ID(y) (N(1.1), N(.25), N(1e2))))"
363 " OR IN(ID(z) (S(a), S(b))))")
365 tree = parser.parse("x in (1,2,5..15) AND y NOT IN (-100..100:10)")
366 result = tree.visit(visitor)
367 self.assertEqual(result, "B(IN(ID(x) (N(1), N(2), R(5..15))) AND !IN(ID(y) (R(-100..100:10))))")
370if __name__ == "__main__": 370 ↛ 371line 370 didn't jump to line 371, because the condition on line 370 was never true
371 unittest.main()