Coverage for tests/test_cliUtilSplitKv.py: 16%

140 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-08-12 09:20 +0000

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 unittest 

26from functools import partial 

27from unittest.mock import MagicMock 

28 

29import click 

30from lsst.daf.butler.cli.utils import LogCliRunner, clickResultMsg, 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 """ 

44 self.assertEqual(split_kv("context", "param", "first=1", return_type=tuple), (("first", "1"),)) 

45 

46 def test_multiple_dict(self): 

47 """Test that multiple comma separated kv pairs convert to a dict.""" 

48 self.assertEqual(split_kv("context", "param", "first=1,second=2"), {"first": "1", "second": "2"}) 

49 

50 def test_multiple_tuple(self): 

51 """Test that multiple comma separated kv pairs convert to a tuple when 

52 return_type=tuple. 

53 """ 

54 self.assertEqual( 

55 split_kv("context", "param", "first=1,second=2", return_type=tuple), 

56 (("first", "1"), ("second", "2")), 

57 ) 

58 

59 def test_unseparated(self): 

60 """Test that a value without a key converts to a kv pair with an empty 

61 string key. 

62 """ 

63 self.assertEqual( 

64 split_kv("context", "param", "first,second=2", unseparated_okay=True), 

65 {"": "first", "second": "2"}, 

66 ) 

67 

68 def test_notMultiple(self): 

69 """Test that multiple values are rejected if multiple=False.""" 

70 with self.assertRaisesRegex( 

71 click.ClickException, 

72 "Could not parse key-value pair " 

73 "'first=1,second=2' using separator '=', with multiple values not " 

74 "allowed.", 

75 ): 

76 split_kv("context", "param", "first=1,second=2", multiple=False) 

77 

78 def test_wrongSeparator(self): 

79 """Test that an input with the wrong separator raises.""" 

80 with self.assertRaises(click.ClickException): 

81 split_kv("context", "param", "first-1") 

82 

83 def test_missingSeparator(self): 

84 """Test that an input with no separator raises when 

85 unseparated_okay=False (this is the default value). 

86 """ 

87 with self.assertRaises(click.ClickException): 

88 split_kv("context", "param", "first 1") 

89 

90 def test_unseparatedOkay(self): 

91 """Test that that the default key is used for values without a 

92 separator when unseparated_okay=True. 

93 """ 

94 self.assertEqual(split_kv("context", "param", "foo", unseparated_okay=True), {"": "foo"}) 

95 

96 def test_unseparatedOkay_list(self): 

97 """Test that that the default key is used for values without a 

98 separator when unseparated_okay=True and the return_type is tuple. 

99 """ 

100 self.assertEqual( 

101 split_kv("context", "param", "foo,bar", unseparated_okay=True, return_type=tuple), 

102 (("", "foo"), ("", "bar")), 

103 ) 

104 

105 def test_unseparatedOkay_defaultKey(self): 

106 """Test that that the default key can be set and is used for values 

107 without a separator when unseparated_okay=True. 

108 """ 

109 self.assertEqual( 

110 split_kv("context", "param", "foo", unseparated_okay=True, default_key=...), {...: "foo"} 

111 ) 

112 

113 def test_dashSeparator(self): 

114 """Test that specifying a separator is accepted and converts arguments 

115 to a dict. 

116 """ 

117 self.assertEqual( 

118 split_kv("context", "param", "first-1,second-2", separator="-"), {"first": "1", "second": "2"} 

119 ) 

120 

121 def test_reverseKv(self): 

122 self.assertEqual( 

123 split_kv( 

124 "context", 

125 "param", 

126 "first=1,second", 

127 unseparated_okay=True, 

128 default_key="key", 

129 reverse_kv=True, 

130 ), 

131 {"1": "first", "second": "key"}, 

132 ) 

133 

134 def test_invalidResultType(self): 

135 with self.assertRaises(click.ClickException): 

136 split_kv( 

137 "context", 

138 "param", 

139 "first=1,second=2", 

140 return_type=set, 

141 ) 

142 

143 

144class SplitKvCmdTestCase(unittest.TestCase): 

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

146 

147 def setUp(self): 

148 self.runner = LogCliRunner() 

149 

150 def test_cli(self): 

151 mock = MagicMock() 

152 

153 @click.command() 

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

155 def cli(value): 

156 mock(value) 

157 

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

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

160 mock.assert_called_with({"first": "1"}) 

161 

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

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

164 mock.assert_called_with({"first": "1", "second": "2"}) 

165 

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

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

168 mock.assert_called_with({"first": "1", "second": "2"}) 

169 

170 # double separator "==" should fail: 

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

172 self.assertEqual(result.exit_code, 1) 

173 self.assertEqual( 

174 result.output, 

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

176 "multiple values allowed: too many values to unpack (expected 2)\n", 

177 ) 

178 

179 def test_choice(self): 

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

181 mock = MagicMock() 

182 

183 @click.command() 

184 @click.option( 

185 "--metasyntactic-var", 

186 callback=partial( 

187 split_kv, 

188 unseparated_okay=True, 

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

190 normalize=True, 

191 ), 

192 ) 

193 def cli(metasyntactic_var): 

194 mock(metasyntactic_var) 

195 

196 # check a valid choice without a kv separator 

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

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

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

200 

201 # check a valid choice with a kv separator 

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

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

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

205 

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

207 # return a non-zero exit code. 

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

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

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

211 

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

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

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

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

216 

217 def test_separatorDash(self): 

218 def split_kv_dash(context, param, values): 

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

220 

221 mock = MagicMock() 

222 

223 @click.command() 

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

225 def cli(value): 

226 mock(value) 

227 

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

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

230 mock.assert_called_with({"first": "1"}) 

231 

232 def test_separatorFunctoolsDash(self): 

233 mock = MagicMock() 

234 

235 @click.command() 

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

237 def cli(value): 

238 mock(value) 

239 

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

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

242 mock.assert_called_with({"first": "1", "second": "2"}) 

243 

244 def test_separatorSpace(self): 

245 @click.command() 

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

247 def cli(value): 

248 pass 

249 

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

251 self.assertEqual(str(result.exception), "' ' is not a supported separator for key-value pairs.") 

252 

253 def test_separatorComma(self): 

254 @click.command() 

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

256 def cli(value): 

257 pass 

258 

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

260 self.assertEqual(str(result.exception), "',' is not a supported separator for key-value pairs.") 

261 

262 def test_normalizeWithoutChoice(self): 

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

264 

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

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

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

268 anything. 

269 """ 

270 mock = MagicMock() 

271 

272 @click.command() 

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

274 def cli(value): 

275 mock(value) 

276 

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

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

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

280 

281 def test_addToDefaultValue(self): 

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

283 added to the default value set in the option. 

284 """ 

285 mock = MagicMock() 

286 

287 @click.command() 

288 @click.option( 

289 "--value", 

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

291 default=["INFO"], 

292 multiple=True, 

293 ) 

294 def cli(value): 

295 mock(value) 

296 

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

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

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

300 

301 def test_replaceDefaultValue(self): 

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

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

304 different. 

305 """ 

306 mock = MagicMock() 

307 

308 @click.command() 

309 @click.option( 

310 "--value", callback=partial(split_kv, unseparated_okay=True), default=["INFO"], multiple=True 

311 ) 

312 def cli(value): 

313 mock(value) 

314 

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

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

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

318 

319 

320if __name__ == "__main__": 

321 unittest.main()