Coverage for tests/test_cliUtilSplitKv.py : 23%

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# (http://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 <http://www.gnu.org/licenses/>.
22"""Unit tests for the daf_butler shared CLI options.
23"""
25import click
26from functools import partial
27import unittest
28from unittest.mock import MagicMock
30from lsst.daf.butler.cli.utils import clickResultMsg, LogCliRunner, split_kv
33class SplitKvTestCase(unittest.TestCase):
34 """Tests that call split_kv directly."""
36 def test_single_dict(self):
37 """Test that a single kv pair converts to a dict."""
38 self.assertEqual(split_kv("context", "param", "first=1"), {"first": "1"})
40 def test_single_tuple(self):
41 """Test that a single kv pair converts to a tuple when
42 return_type=tuple."""
43 self.assertEqual(split_kv("context", "param", "first=1", return_type=tuple), (("first", "1"),))
45 def test_multiple_dict(self):
46 """Test that multiple comma separated kv pairs convert to a dict."""
47 self.assertEqual(split_kv("context", "param", "first=1,second=2"), {"first": "1", "second": "2"})
49 def test_multiple_tuple(self):
50 """Test that multiple comma separated kv pairs convert to a tuple when
51 return_type=tuple."""
52 self.assertEqual(split_kv("context", "param", "first=1,second=2", return_type=tuple),
53 (("first", "1"), ("second", "2")))
55 def test_unseparated(self):
56 """Test that a value without a key converts to a kv pair with an empty
57 string key."""
58 self.assertEqual(split_kv("context", "param", "first,second=2", unseparated_okay=True),
59 {"": "first", "second": "2"})
61 def test_notMultiple(self):
62 """Test that multiple values are rejected if multiple=False."""
63 with self.assertRaisesRegex(click.ClickException, "Could not parse key-value pair "
64 "'first=1,second=2' using separator '=', with multiple values not "
65 "allowed."):
66 split_kv("context", "param", "first=1,second=2", multiple=False)
68 def test_wrongSeparator(self):
69 """Test that an input with the wrong separator raises."""
70 with self.assertRaises(click.ClickException):
71 split_kv("context", "param", "first-1")
73 def test_missingSeparator(self):
74 """Test that an input with no separator raises when
75 unseparated_okay=False (this is the default value)."""
76 with self.assertRaises(click.ClickException):
77 split_kv("context", "param", "first 1")
79 def test_unseparatedOkay(self):
80 """Test that that the default key is used for values without a
81 separator when unseparated_okay=True."""
82 self.assertEqual(split_kv("context", "param", "foo", unseparated_okay=True),
83 {"": "foo"})
85 def test_unseparatedOkay_list(self):
86 """Test that that the default key is used for values without a
87 separator when unseparated_okay=True and the return_type is tuple."""
88 self.assertEqual(split_kv("context", "param", "foo,bar", unseparated_okay=True, return_type=tuple),
89 (("", "foo"), ("", "bar")))
91 def test_unseparatedOkay_defaultKey(self):
92 """Test that that the default key can be set and is used for values
93 without a separator when unseparated_okay=True."""
94 self.assertEqual(split_kv("context", "param", "foo", unseparated_okay=True, default_key=...),
95 {...: "foo"})
97 def test_dashSeparator(self):
98 """Test that specifying a spearator is accepted and converts to a dict.
99 """
100 self.assertEqual(split_kv("context", "param", "first-1,second-2", separator="-"),
101 {"first": "1", "second": "2"})
103 def test_reverseKv(self):
104 self.assertEqual(split_kv("context", "param", "first=1,second", unseparated_okay=True,
105 default_key="key", reverse_kv=True),
106 {"1": "first", "second": "key"})
109class SplitKvCmdTestCase(unittest.TestCase):
110 """Tests using split_kv with a command."""
112 def setUp(self):
113 self.runner = LogCliRunner()
115 def test_cli(self):
116 mock = MagicMock()
118 @click.command()
119 @click.option("--value", callback=split_kv, multiple=True)
120 def cli(value):
121 mock(value)
123 result = self.runner.invoke(cli, ["--value", "first=1"])
124 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
125 mock.assert_called_with({'first': '1'})
127 result = self.runner.invoke(cli, ["--value", "first=1,second=2"])
128 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
129 mock.assert_called_with({'first': '1', 'second': '2'})
131 result = self.runner.invoke(cli, ["--value", "first=1", "--value", "second=2"])
132 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
133 mock.assert_called_with({'first': '1', 'second': '2'})
135 # double separator "==" should fail:
136 result = self.runner.invoke(cli, ["--value", "first==1"])
137 self.assertEqual(result.exit_code, 1)
138 self.assertEqual(result.output,
139 "Error: Could not parse key-value pair 'first==1' using separator '=', with "
140 "multiple values allowed.\n")
142 def test_choice(self):
143 choices = ["FOO", "BAR", "BAZ"]
144 mock = MagicMock()
146 @click.command()
147 @click.option("--metasyntactic-var",
148 callback=partial(split_kv,
149 unseparated_okay=True,
150 choice=click.Choice(choices, case_sensitive=False),
151 normalize=True))
152 def cli(metasyntactic_var):
153 mock(metasyntactic_var)
155 # check a valid choice without a kv separator
156 result = self.runner.invoke(cli, ["--metasyntactic-var", "FOO"])
157 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
158 mock.assert_called_with({"": "FOO"})
160 # check a valid choice with a kv separator
161 result = self.runner.invoke(cli, ["--metasyntactic-var", "lsst.daf.butler=BAR"])
162 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
163 mock.assert_called_with({"lsst.daf.butler": "BAR"})
165 # check invalid choices with and wihtout kv separators
166 for val in ("BOZ", "lsst.daf.butler=BOZ"):
167 result = self.runner.invoke(cli, ["--metasyntactic-var", val])
168 self.assertNotEqual(result.exit_code, 0, msg=clickResultMsg(result))
169 self.assertRegex(result.output,
170 r"Error: Invalid value for ['\"]\-\-metasyntactic-var['\"]:")
171 self.assertIn(f" invalid choice: BOZ. (choose from {', '.join(choices)})",
172 result.output)
174 # check value normalization (lower case "foo" should become "FOO")
175 result = self.runner.invoke(cli, ["--metasyntactic-var", "lsst.daf.butler=foo"])
176 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
177 mock.assert_called_with({"lsst.daf.butler": "FOO"})
179 def test_separatorDash(self):
180 def split_kv_dash(context, param, values):
181 return split_kv(context, param, values, separator="-")
183 mock = MagicMock()
185 @click.command()
186 @click.option("--value", callback=split_kv_dash, multiple=True)
187 def cli(value):
188 mock(value)
190 result = self.runner.invoke(cli, ["--value", "first-1"])
191 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
192 mock.assert_called_with({'first': '1'})
194 def test_separatorFunctoolsDash(self):
195 mock = MagicMock()
197 @click.command()
198 @click.option("--value", callback=partial(split_kv, separator="-"), multiple=True)
199 def cli(value):
200 mock(value)
201 result = self.runner.invoke(cli, ["--value", "first-1", "--value", "second-2"])
202 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
203 mock.assert_called_with({'first': '1', 'second': '2'})
205 def test_separatorSpace(self):
206 @click.command()
207 @click.option("--value", callback=partial(split_kv, separator=" "), multiple=True)
208 def cli(value):
209 pass
210 result = self.runner.invoke(cli, ["--value", "first 1"])
211 self.assertEqual(str(result.exception),
212 "' ' is not a supported separator for key-value pairs.")
214 def test_separatorComma(self):
215 @click.command()
216 @click.option("--value", callback=partial(split_kv, separator=","), multiple=True)
217 def cli(value):
218 pass
219 result = self.runner.invoke(cli, ["--value", "first,1"])
220 self.assertEqual(str(result.exception),
221 "',' is not a supported separator for key-value pairs.")
223 def test_normalizeWithoutChoice(self):
224 """Test that normalize=True without Choice fails gracefully.
226 Normalize uses values in the provided Choice to create the normalized
227 value. Without a provided Choice, it can't normalize. Verify that this
228 does not cause a crash or other bad behavior, it just doesn't normalize
229 anything.
230 """
231 mock = MagicMock()
233 @click.command()
234 @click.option("--value", callback=partial(split_kv, normalize=True))
235 def cli(value):
236 mock(value)
237 result = self.runner.invoke(cli, ["--value", "foo=bar"])
238 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
239 mock.assert_called_with(dict(foo="bar"))
241 def test_addToDefaultValue(self):
242 """Verify that if add_to_default is True that passed-in values are
243 added to the default value set in the option.
244 """
245 mock = MagicMock()
247 @click.command()
248 @click.option("--value",
249 callback=partial(split_kv, add_to_default=True, unseparated_okay=True),
250 default=["INFO"],
251 multiple=True)
252 def cli(value):
253 mock(value)
255 result = self.runner.invoke(cli, ["--value", "lsst.daf.butler=DEBUG"])
256 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
257 mock.assert_called_with({"": "INFO", "lsst.daf.butler": "DEBUG"})
259 def test_replaceDefaultValue(self):
260 """Verify that if add_to_default is False (this is the default value),
261 that passed-in values replace any default value, even if keys are
262 different.
263 """
264 mock = MagicMock()
266 @click.command()
267 @click.option("--value",
268 callback=partial(split_kv, unseparated_okay=True),
269 default=["INFO"],
270 multiple=True)
271 def cli(value):
272 mock(value)
274 result = self.runner.invoke(cli, ["--value", "lsst.daf.butler=DEBUG"])
275 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result))
276 mock.assert_called_with({"lsst.daf.butler": "DEBUG"})
279if __name__ == "__main__": 279 ↛ 280line 279 didn't jump to line 280, because the condition on line 279 was never true
280 unittest.main()