Coverage for tests/test_cliUtilSplitKv.py: 21%

140 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-02 09:50 +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 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( 

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

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

55 ) 

56 

57 def test_unseparated(self): 

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

59 string key.""" 

60 self.assertEqual( 

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

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

63 ) 

64 

65 def test_notMultiple(self): 

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

67 with self.assertRaisesRegex( 

68 click.ClickException, 

69 "Could not parse key-value pair " 

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

71 "allowed.", 

72 ): 

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

74 

75 def test_wrongSeparator(self): 

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

77 with self.assertRaises(click.ClickException): 

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

79 

80 def test_missingSeparator(self): 

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

82 unseparated_okay=False (this is the default value).""" 

83 with self.assertRaises(click.ClickException): 

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

85 

86 def test_unseparatedOkay(self): 

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

88 separator when unseparated_okay=True.""" 

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

90 

91 def test_unseparatedOkay_list(self): 

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

93 separator when unseparated_okay=True and the return_type is tuple.""" 

94 self.assertEqual( 

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

96 (("", "foo"), ("", "bar")), 

97 ) 

98 

99 def test_unseparatedOkay_defaultKey(self): 

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

101 without a separator when unseparated_okay=True.""" 

102 self.assertEqual( 

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

104 ) 

105 

106 def test_dashSeparator(self): 

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

108 to a dict. 

109 """ 

110 self.assertEqual( 

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

112 ) 

113 

114 def test_reverseKv(self): 

115 self.assertEqual( 

116 split_kv( 

117 "context", 

118 "param", 

119 "first=1,second", 

120 unseparated_okay=True, 

121 default_key="key", 

122 reverse_kv=True, 

123 ), 

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

125 ) 

126 

127 def test_invalidResultType(self): 

128 with self.assertRaises(click.ClickException): 

129 split_kv( 

130 "context", 

131 "param", 

132 "first=1,second=2", 

133 return_type=set, 

134 ) 

135 

136 

137class SplitKvCmdTestCase(unittest.TestCase): 

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

139 

140 def setUp(self): 

141 self.runner = LogCliRunner() 

142 

143 def test_cli(self): 

144 mock = MagicMock() 

145 

146 @click.command() 

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

148 def cli(value): 

149 mock(value) 

150 

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

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

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

154 

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

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

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

158 

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

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

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

162 

163 # double separator "==" should fail: 

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

165 self.assertEqual(result.exit_code, 1) 

166 self.assertEqual( 

167 result.output, 

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

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

170 ) 

171 

172 def test_choice(self): 

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

174 mock = MagicMock() 

175 

176 @click.command() 

177 @click.option( 

178 "--metasyntactic-var", 

179 callback=partial( 

180 split_kv, 

181 unseparated_okay=True, 

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

183 normalize=True, 

184 ), 

185 ) 

186 def cli(metasyntactic_var): 

187 mock(metasyntactic_var) 

188 

189 # check a valid choice without a kv separator 

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

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

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

193 

194 # check a valid choice with a kv separator 

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

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

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

198 

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

200 # return a non-zero exit code. 

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

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

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

204 

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

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

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

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

209 

210 def test_separatorDash(self): 

211 def split_kv_dash(context, param, values): 

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

213 

214 mock = MagicMock() 

215 

216 @click.command() 

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

218 def cli(value): 

219 mock(value) 

220 

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

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

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

224 

225 def test_separatorFunctoolsDash(self): 

226 mock = MagicMock() 

227 

228 @click.command() 

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

230 def cli(value): 

231 mock(value) 

232 

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

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

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

236 

237 def test_separatorSpace(self): 

238 @click.command() 

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

240 def cli(value): 

241 pass 

242 

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

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

245 

246 def test_separatorComma(self): 

247 @click.command() 

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

249 def cli(value): 

250 pass 

251 

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

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

254 

255 def test_normalizeWithoutChoice(self): 

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

257 

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

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

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

261 anything. 

262 """ 

263 mock = MagicMock() 

264 

265 @click.command() 

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

267 def cli(value): 

268 mock(value) 

269 

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

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

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

273 

274 def test_addToDefaultValue(self): 

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

276 added to the default value set in the option. 

277 """ 

278 mock = MagicMock() 

279 

280 @click.command() 

281 @click.option( 

282 "--value", 

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

284 default=["INFO"], 

285 multiple=True, 

286 ) 

287 def cli(value): 

288 mock(value) 

289 

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

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

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

293 

294 def test_replaceDefaultValue(self): 

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

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

297 different. 

298 """ 

299 mock = MagicMock() 

300 

301 @click.command() 

302 @click.option( 

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

304 ) 

305 def cli(value): 

306 mock(value) 

307 

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

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

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

311 

312 

313if __name__ == "__main__": 

314 unittest.main()