Hide keyboard shortcuts

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/>. 

21 

22"""Unit tests for the daf_butler shared CLI options. 

23""" 

24 

25import click 

26from functools import partial 

27import unittest 

28from unittest.mock import MagicMock 

29 

30from lsst.daf.butler.cli.utils import clickResultMsg, LogCliRunner, split_kv 

31 

32 

33class SplitKvTestCase(unittest.TestCase): 

34 """Tests that call split_kv directly.""" 

35 

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"}) 

39 

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"),)) 

44 

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"}) 

48 

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"))) 

54 

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"}) 

60 

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) 

67 

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") 

72 

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") 

78 

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"}) 

84 

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"))) 

90 

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"}) 

96 

97 def test_duplicateKeys(self): 

98 # todo don't we want dulicate keys to aggregate into a list? 

99 """Test that values with duplicate keys raise.""" 

100 with self.assertRaises(click.ClickException): 

101 split_kv("context", "param", "first=1,first=2") 

102 

103 def test_dashSeparator(self): 

104 """Test that specifying a spearator is accepted and converts to a dict. 

105 """ 

106 self.assertEqual(split_kv("context", "param", "first-1,second-2", separator="-"), 

107 {"first": "1", "second": "2"}) 

108 

109 def test_reverseKv(self): 

110 self.assertEqual(split_kv("context", "param", "first=1,second", unseparated_okay=True, 

111 default_key="key", reverse_kv=True), 

112 {"1": "first", "second": "key"}) 

113 

114 

115class SplitKvCmdTestCase(unittest.TestCase): 

116 """Tests using split_kv with a command.""" 

117 

118 def setUp(self): 

119 self.runner = LogCliRunner() 

120 

121 def test_cli(self): 

122 mock = MagicMock() 

123 

124 @click.command() 

125 @click.option("--value", callback=split_kv, multiple=True) 

126 def cli(value): 

127 mock(value) 

128 

129 result = self.runner.invoke(cli, ["--value", "first=1"]) 

130 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

131 mock.assert_called_with({'first': '1'}) 

132 

133 result = self.runner.invoke(cli, ["--value", "first=1,second=2"]) 

134 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

135 mock.assert_called_with({'first': '1', 'second': '2'}) 

136 

137 result = self.runner.invoke(cli, ["--value", "first=1", "--value", "second=2"]) 

138 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

139 mock.assert_called_with({'first': '1', 'second': '2'}) 

140 

141 # double separator "==" should fail: 

142 result = self.runner.invoke(cli, ["--value", "first==1"]) 

143 self.assertEqual(result.exit_code, 1) 

144 self.assertEqual(result.output, 

145 "Error: Could not parse key-value pair 'first==1' using separator '=', with " 

146 "multiple values allowed.\n") 

147 

148 def test_choice(self): 

149 choices = ["FOO", "BAR", "BAZ"] 

150 mock = MagicMock() 

151 

152 @click.command() 

153 @click.option("--metasyntactic-var", 

154 callback=partial(split_kv, 

155 unseparated_okay=True, 

156 choice=click.Choice(choices, case_sensitive=False), 

157 normalize=True)) 

158 def cli(metasyntactic_var): 

159 mock(metasyntactic_var) 

160 

161 # check a valid choice without a kv separator 

162 result = self.runner.invoke(cli, ["--metasyntactic-var", "FOO"]) 

163 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

164 mock.assert_called_with({"": "FOO"}) 

165 

166 # check a valid choice with a kv separator 

167 result = self.runner.invoke(cli, ["--metasyntactic-var", "lsst.daf.butler=BAR"]) 

168 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

169 mock.assert_called_with({"lsst.daf.butler": "BAR"}) 

170 

171 # check invalid choices with and wihtout kv separators 

172 for val in ("BOZ", "lsst.daf.butler=BOZ"): 

173 result = self.runner.invoke(cli, ["--metasyntactic-var", val]) 

174 self.assertNotEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

175 self.assertRegex(result.output, 

176 r"Error: Invalid value for ['\"]\-\-metasyntactic-var['\"]:") 

177 self.assertIn(f" invalid choice: BOZ. (choose from {', '.join(choices)})", 

178 result.output) 

179 

180 # check value normalization (lower case "foo" should become "FOO") 

181 result = self.runner.invoke(cli, ["--metasyntactic-var", "lsst.daf.butler=foo"]) 

182 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

183 mock.assert_called_with({"lsst.daf.butler": "FOO"}) 

184 

185 def test_separatorDash(self): 

186 def split_kv_dash(context, param, values): 

187 return split_kv(context, param, values, separator="-") 

188 

189 mock = MagicMock() 

190 

191 @click.command() 

192 @click.option("--value", callback=split_kv_dash, multiple=True) 

193 def cli(value): 

194 mock(value) 

195 

196 result = self.runner.invoke(cli, ["--value", "first-1"]) 

197 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

198 mock.assert_called_with({'first': '1'}) 

199 

200 def test_separatorFunctoolsDash(self): 

201 mock = MagicMock() 

202 

203 @click.command() 

204 @click.option("--value", callback=partial(split_kv, separator="-"), multiple=True) 

205 def cli(value): 

206 mock(value) 

207 result = self.runner.invoke(cli, ["--value", "first-1", "--value", "second-2"]) 

208 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

209 mock.assert_called_with({'first': '1', 'second': '2'}) 

210 

211 def test_separatorSpace(self): 

212 @click.command() 

213 @click.option("--value", callback=partial(split_kv, separator=" "), multiple=True) 

214 def cli(value): 

215 pass 

216 result = self.runner.invoke(cli, ["--value", "first 1"]) 

217 self.assertEqual(str(result.exception), 

218 "' ' is not a supported separator for key-value pairs.") 

219 

220 def test_separatorComma(self): 

221 @click.command() 

222 @click.option("--value", callback=partial(split_kv, separator=","), multiple=True) 

223 def cli(value): 

224 pass 

225 result = self.runner.invoke(cli, ["--value", "first,1"]) 

226 self.assertEqual(str(result.exception), 

227 "',' is not a supported separator for key-value pairs.") 

228 

229 def test_normalizeWithoutChoice(self): 

230 """Test that normalize=True without Choice fails gracefully. 

231 

232 Normalize uses values in the provided Choice to create the normalized 

233 value. Without a provided Choice, it can't normalize. Verify that this 

234 does not cause a crash or other bad behavior, it just doesn't normalize 

235 anything. 

236 """ 

237 mock = MagicMock() 

238 

239 @click.command() 

240 @click.option("--value", callback=partial(split_kv, normalize=True)) 

241 def cli(value): 

242 mock(value) 

243 result = self.runner.invoke(cli, ["--value", "foo=bar"]) 

244 self.assertEqual(result.exit_code, 0, msg=clickResultMsg(result)) 

245 mock.assert_called_with(dict(foo="bar")) 

246 

247 

248if __name__ == "__main__": 248 ↛ 249line 248 didn't jump to line 249, because the condition on line 248 was never true

249 unittest.main()