Coverage for tests / test_pipeline_graph_expressions.py: 34%
38 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 08:59 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 08:59 +0000
1# This file is part of pipe_base.
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 software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <https://www.gnu.org/licenses/>.
28from __future__ import annotations
30import unittest
32import lsst.pipe.base.pipeline_graph.expressions as pge
33import lsst.utils.tests
34from lsst.pipe.base.pipeline_graph import InvalidExpressionError
37class PipelineGraphExpressionParserTestCase(unittest.TestCase):
38 """Test for parsing the small expression language on pipeline subsets."""
40 def test_identifiers(self):
41 """Test identifiers of various forms."""
42 self.assertEqual(pge.parse("a_2"), pge.IdentifierNode(qualifier=None, label="a_2"))
43 self.assertEqual(pge.parse("T:b_3"), pge.IdentifierNode(qualifier="T", label="b_3"))
44 self.assertEqual(pge.parse("D:c_4"), pge.IdentifierNode(qualifier="D", label="c_4"))
45 self.assertEqual(pge.parse("S:d_5"), pge.IdentifierNode(qualifier="S", label="d_5"))
46 with self.assertRaises(InvalidExpressionError):
47 pge.parse("a+3")
48 with self.assertRaises(InvalidExpressionError):
49 pge.parse("G:d1")
51 def test_directions(self):
52 """Test ancestor/descendent expressions."""
53 self.assertEqual(pge.parse("<a"), pge.DirectionNode("<", pge.IdentifierNode(None, "a")))
54 self.assertEqual(pge.parse(">a"), pge.DirectionNode(">", pge.IdentifierNode(None, "a")))
55 self.assertEqual(pge.parse("<= a"), pge.DirectionNode("<=", pge.IdentifierNode(None, "a")))
56 self.assertEqual(pge.parse(">= a"), pge.DirectionNode(">=", pge.IdentifierNode(None, "a")))
58 def test_binary(self):
59 """Test binary operators, including precedence."""
60 self.assertEqual(
61 pge.parse("a | b"),
62 pge.UnionNode(pge.IdentifierNode(None, "a"), pge.IdentifierNode(None, "b")),
63 )
64 self.assertEqual(
65 pge.parse("a & b"),
66 pge.IntersectionNode(pge.IdentifierNode(None, "a"), pge.IdentifierNode(None, "b")),
67 )
68 self.assertEqual(
69 pge.parse("a | b & c"),
70 pge.UnionNode(
71 pge.IdentifierNode(None, "a"),
72 pge.IntersectionNode(pge.IdentifierNode(None, "b"), pge.IdentifierNode(None, "c")),
73 ),
74 )
75 self.assertEqual(
76 pge.parse("a & b | c"),
77 pge.UnionNode(
78 pge.IntersectionNode(pge.IdentifierNode(None, "a"), pge.IdentifierNode(None, "b")),
79 pge.IdentifierNode(None, "c"),
80 ),
81 )
83 def test_not(self):
84 """Test set inversion."""
85 self.assertEqual(
86 pge.parse("~a"),
87 pge.NotNode(pge.IdentifierNode(None, "a")),
88 )
89 self.assertEqual(
90 pge.parse("~<b"),
91 pge.NotNode(pge.DirectionNode("<", pge.IdentifierNode(None, "b"))),
92 )
94 def test_complex(self):
95 """Test a large expression with nested parentheses."""
96 self.assertEqual(
97 pge.parse("(a & ~(b | <c)) | ~>=d"),
98 pge.UnionNode(
99 pge.IntersectionNode(
100 pge.IdentifierNode(None, "a"),
101 pge.NotNode(
102 pge.UnionNode(
103 pge.IdentifierNode(None, "b"),
104 pge.DirectionNode("<", pge.IdentifierNode(None, "c")),
105 )
106 ),
107 ),
108 pge.NotNode(pge.DirectionNode(">=", pge.IdentifierNode(None, "d"))),
109 ),
110 )
112 def test_lex_errors(self):
113 """Test expressions that should cause a lexer error."""
114 with self.assertRaisesRegex(InvalidExpressionError, "near character 9: '!'"):
115 pge.parse("frobbler !b")
117 def test_parser_errors(self):
118 """Test expressions that should cause a parser error."""
119 with self.assertRaisesRegex(InvalidExpressionError, "near character 2: '|'"):
120 pge.parse("< | c")
121 with self.assertRaisesRegex(InvalidExpressionError, "Expression ended"):
122 pge.parse("d |")
125if __name__ == "__main__":
126 lsst.utils.tests.init()
127 unittest.main()