Coverage for tests/test_cliUtilSplitKv.py: 23%

Shortcuts 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

139 statements  

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_dashSeparator(self): 

98 """Test that specifying a separator 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"}) 

102 

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

107 

108 

109class SplitKvCmdTestCase(unittest.TestCase): 

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

111 

112 def setUp(self): 

113 self.runner = LogCliRunner() 

114 

115 def test_cli(self): 

116 mock = MagicMock() 

117 

118 @click.command() 

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

120 def cli(value): 

121 mock(value) 

122 

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

126 

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

130 

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

134 

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

141 

142 def test_choice(self): 

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

144 mock = MagicMock() 

145 

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) 

154 

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

159 

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

164 

165 # check that invalid choices with and without kv separators fail & 

166 # return a non-zero exit code. 

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

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

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

170 

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

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

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

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

175 

176 def test_separatorDash(self): 

177 def split_kv_dash(context, param, values): 

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

179 

180 mock = MagicMock() 

181 

182 @click.command() 

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

184 def cli(value): 

185 mock(value) 

186 

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

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

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

190 

191 def test_separatorFunctoolsDash(self): 

192 mock = MagicMock() 

193 

194 @click.command() 

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

196 def cli(value): 

197 mock(value) 

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

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

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

201 

202 def test_separatorSpace(self): 

203 @click.command() 

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

205 def cli(value): 

206 pass 

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

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

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

210 

211 def test_separatorComma(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_normalizeWithoutChoice(self): 

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

222 

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

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

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

226 anything. 

227 """ 

228 mock = MagicMock() 

229 

230 @click.command() 

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

232 def cli(value): 

233 mock(value) 

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

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

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

237 

238 def test_addToDefaultValue(self): 

239 """Verify that if add_to_default is True that passed-in values are 

240 added to the default value set in the option. 

241 """ 

242 mock = MagicMock() 

243 

244 @click.command() 

245 @click.option("--value", 

246 callback=partial(split_kv, add_to_default=True, unseparated_okay=True), 

247 default=["INFO"], 

248 multiple=True) 

249 def cli(value): 

250 mock(value) 

251 

252 result = self.runner.invoke(cli, ["--value", "lsst.daf.butler=DEBUG"]) 

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

254 mock.assert_called_with({"": "INFO", "lsst.daf.butler": "DEBUG"}) 

255 

256 def test_replaceDefaultValue(self): 

257 """Verify that if add_to_default is False (this is the default value), 

258 that passed-in values replace any default value, even if keys are 

259 different. 

260 """ 

261 mock = MagicMock() 

262 

263 @click.command() 

264 @click.option("--value", 

265 callback=partial(split_kv, unseparated_okay=True), 

266 default=["INFO"], 

267 multiple=True) 

268 def cli(value): 

269 mock(value) 

270 

271 result = self.runner.invoke(cli, ["--value", "lsst.daf.butler=DEBUG"]) 

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

273 mock.assert_called_with({"lsst.daf.butler": "DEBUG"}) 

274 

275 

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

277 unittest.main()