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 

26import unittest 

27from unittest.mock import MagicMock 

28 

29 

30from lsst.daf.butler.cli import butler 

31from lsst.daf.butler.cli.utils import (clickResultMsg, LogCliRunner, Mocker, mockEnvVar, MWArgumentDecorator, 

32 MWOption, MWOptionDecorator, MWPath, option_section, unwrap) 

33from lsst.daf.butler.cli.opt import directory_argument, repo_argument 

34 

35 

36class MockerTestCase(unittest.TestCase): 

37 

38 def test_callMock(self): 

39 """Test that a mocked subcommand calls the Mocker and can be verified. 

40 """ 

41 runner = LogCliRunner(env=mockEnvVar) 

42 result = runner.invoke(butler.cli, ["create", "repo"]) 

43 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

44 Mocker.mock.assert_called_with(repo="repo", seed_config=None, dimension_config=None, 

45 standalone=False, override=False, outfile=None) 

46 

47 

48class ArgumentHelpGeneratorTestCase(unittest.TestCase): 

49 

50 @staticmethod 

51 @click.command() 

52 # Use custom help in the arguments so that any changes to default help text 

53 # do not break this test unnecessarily. 

54 @repo_argument(help="repo help text") 

55 @directory_argument(help="directory help text") 

56 def cli(): 

57 """The cli help message.""" 

58 pass 

59 

60 def test_help(self): 

61 """Tests `utils.addArgumentHelp` and its use in repo_argument and 

62 directory_argument; verifies that the argument help gets added to the 

63 command fucntion help, and that it's added in the correct order. See 

64 addArgumentHelp for more details.""" 

65 runner = LogCliRunner() 

66 result = runner.invoke(ArgumentHelpGeneratorTestCase.cli, ["--help"]) 

67 expected = """Usage: cli [OPTIONS] REPO DIRECTORY 

68 

69 The cli help message. 

70 

71 repo help text 

72 

73 directory help text 

74 

75Options: 

76 --help Show this message and exit. 

77""" 

78 self.assertIn(expected, result.output) 

79 

80 

81class UnwrapStringTestCase(unittest.TestCase): 

82 

83 def test_leadingNewline(self): 

84 testStr = """ 

85 foo bar 

86 baz """ 

87 self.assertEqual(unwrap(testStr), "foo bar baz") 

88 

89 def test_leadingContent(self): 

90 testStr = """foo bar 

91 baz """ 

92 self.assertEqual(unwrap(testStr), "foo bar baz") 

93 

94 def test_trailingNewline(self): 

95 testStr = """ 

96 foo bar 

97 baz 

98 """ 

99 self.assertEqual(unwrap(testStr), "foo bar baz") 

100 

101 def test_oneLine(self): 

102 testStr = """foo bar baz""" 

103 self.assertEqual(unwrap(testStr), "foo bar baz") 

104 

105 def test_oneLineWithLeading(self): 

106 testStr = """ 

107 foo bar baz""" 

108 self.assertEqual(unwrap(testStr), "foo bar baz") 

109 

110 def test_oneLineWithTrailing(self): 

111 testStr = """foo bar baz 

112 """ 

113 self.assertEqual(unwrap(testStr), "foo bar baz") 

114 

115 def test_lineBreaks(self): 

116 testStr = """foo bar 

117 baz 

118 

119 boz 

120 

121 qux""" 

122 self.assertEqual(unwrap(testStr), "foo bar baz\n\nboz\n\nqux") 

123 

124 

125class MWOptionTest(unittest.TestCase): 

126 

127 def setUp(self): 

128 self.runner = LogCliRunner() 

129 

130 def test_addElipsisToMultiple(self): 

131 """Verify that MWOption adds elipsis to the option metavar when 

132 `multiple=True` 

133 

134 The default behavior of click is to not add elipsis to options that 

135 have `multiple=True`.""" 

136 

137 @click.command() 

138 @click.option("--things", cls=MWOption, multiple=True) 

139 def cmd(things): 

140 pass 

141 result = self.runner.invoke(cmd, ["--help"]) 

142 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

143 expectedOutut = """Options: 

144 --things TEXT ...""" 

145 self.assertIn(expectedOutut, result.output) 

146 

147 def test_addElipsisToNargs(self): 

148 """Verify that MWOption adds " ..." after the option metavar when 

149 `nargs` is set to more than 1 and less than 1. 

150 

151 The default behavior of click is to add elipsis when nargs does not 

152 equal 1, but it does not put a space before the elipsis and we prefer 

153 a space between the metavar and the elipsis.""" 

154 for numberOfArgs in (0, 1, 2): # nargs must be >= 0 for an option 

155 

156 @click.command() 

157 @click.option("--things", cls=MWOption, nargs=numberOfArgs) 

158 def cmd(things): 

159 pass 

160 result = self.runner.invoke(cmd, ["--help"]) 

161 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

162 expectedOutut = f"""Options: 

163 --things TEXT{' ...' if numberOfArgs != 1 else ''}""" 

164 self.assertIn(expectedOutut, result.output) 

165 

166 

167class MWArgumentDecoratorTest(unittest.TestCase): 

168 """Tests for the MWArgumentDecorator class.""" 

169 

170 things_argument = MWArgumentDecorator("things") 

171 otherHelpText = "Help text for OTHER." 

172 other_argument = MWArgumentDecorator("other", help=otherHelpText) 

173 

174 def setUp(self): 

175 self.runner = LogCliRunner() 

176 

177 def test_help(self): 

178 """Verify expected help text output. 

179 

180 Verify argument help gets inserted after the usage, in the order 

181 arguments are declared. 

182 

183 Verify that MWArgument adds " ..." after the option metavar when 

184 `nargs` != 1. The default behavior of click is to add elipsis when 

185 nargs does not equal 1, but it does not put a space before the elipsis 

186 and we prefer a space between the metavar and the elipsis.""" 

187 # nargs can be -1 for any number of args, or >= 1 for a specified 

188 # number of arguments. 

189 

190 helpText = "Things help text." 

191 for numberOfArgs in (-1, 1, 2): 

192 for required in (True, False): 

193 

194 @click.command() 

195 @self.things_argument(required=required, nargs=numberOfArgs, help=helpText) 

196 @self.other_argument() 

197 def cmd(things, other): 

198 """Cmd help text.""" 

199 pass 

200 result = self.runner.invoke(cmd, ["--help"]) 

201 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

202 expectedOutut = (f"""Usage: cmd [OPTIONS] {'THINGS' if required else '[THINGS]'} {'... ' if numberOfArgs != 1 else ''}OTHER 

203 

204 Cmd help text. 

205 

206 {helpText} 

207 

208 {self.otherHelpText} 

209""") 

210 self.assertIn(expectedOutut, result.output) 

211 

212 def testUse(self): 

213 """Test using the MWArgumentDecorator with a command.""" 

214 mock = MagicMock() 

215 

216 @click.command() 

217 @self.things_argument() 

218 def cli(things): 

219 mock(things) 

220 self.runner = click.testing.CliRunner() 

221 result = self.runner.invoke(cli, ("foo")) 

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

223 mock.assert_called_with("foo") 

224 

225 

226class MWOptionDecoratorTest(unittest.TestCase): 

227 """Tests for the MWOptionDecorator class.""" 

228 

229 test_option = MWOptionDecorator("-t", "--test", multiple=True) 

230 

231 def testGetName(self): 

232 """Test getting the option name from the MWOptionDecorator.""" 

233 self.assertEqual(self.test_option.name(), "test") 

234 

235 def testGetOpts(self): 

236 """Test getting the option flags from the MWOptionDecorator.""" 

237 self.assertEqual(self.test_option.opts(), ["-t", "--test"]) 

238 

239 def testUse(self): 

240 """Test using the MWOptionDecorator with a command.""" 

241 mock = MagicMock() 

242 

243 @click.command() 

244 @self.test_option() 

245 def cli(test): 

246 mock(test) 

247 self.runner = click.testing.CliRunner() 

248 result = self.runner.invoke(cli, ("-t", "foo")) 

249 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

250 mock.assert_called_with(("foo",)) 

251 

252 def testOverride(self): 

253 """Test using the MWOptionDecorator with a command and overriding one 

254 of the default values.""" 

255 mock = MagicMock() 

256 

257 @click.command() 

258 @self.test_option(multiple=False) 

259 def cli(test): 

260 mock(test) 

261 self.runner = click.testing.CliRunner() 

262 result = self.runner.invoke(cli, ("-t", "foo")) 

263 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

264 mock.assert_called_with("foo") 

265 

266 

267class SectionOptionTest(unittest.TestCase): 

268 """Tests for the option_section decorator that inserts section break 

269 headings between options in the --help output of a command.""" 

270 

271 @staticmethod 

272 @click.command() 

273 @click.option("--foo") 

274 @option_section("Section break between metasyntactic variables.") 

275 @click.option("--bar") 

276 def cli(foo, bar): 

277 pass 

278 

279 def setUp(self): 

280 self.runner = click.testing.CliRunner() 

281 

282 def test_section_help(self): 

283 """Verify that the section break is printed in the help output in the 

284 expected location and with expected formatting.""" 

285 result = self.runner.invoke(self.cli, ["--help"]) 

286 # \x20 is a space, added explicity below to prevent the 

287 # normally-helpful editor setting "remove trailing whitespace" from 

288 # stripping it out in this case. (The blank line with 2 spaces is an 

289 # artifact of how click and our code generate help text.) 

290 expected = """Options: 

291 --foo TEXT 

292\x20\x20 

293Section break between metasyntactic variables. 

294 --bar TEXT""" 

295 self.assertIn(expected, result.output) 

296 

297 def test_section_function(self): 

298 """Verify that the section does not cause any arguments to be passed to 

299 the command function. 

300 

301 The command function `cli` implementation inputs `foo` and `bar`, but 

302 does accept an argument for the section. When the command is invoked 

303 and the function called it should result in exit_code=0 (not 1 with a 

304 missing argument error). 

305 """ 

306 result = self.runner.invoke(self.cli, []) 

307 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

308 

309 

310class MWPathTest(unittest.TestCase): 

311 

312 def getCmd(self, exists): 

313 

314 @click.command() 

315 @click.option("--name", type=MWPath(exists=exists)) 

316 def cmd(name): 

317 pass 

318 return cmd 

319 

320 def setUp(self): 

321 self.runner = click.testing.CliRunner() 

322 

323 def test_exist(self): 

324 """Test the exist argument, verify that True means the file must exist, 

325 False means the file must not exist, and None means that the file may 

326 or may not exist.""" 

327 with self.runner.isolated_filesystem(): 

328 mustExistCmd = self.getCmd(exists=True) 

329 mayExistCmd = self.getCmd(exists=None) 

330 mustNotExistCmd = self.getCmd(exists=False) 

331 args = ["--name", "foo.txt"] 

332 

333 result = self.runner.invoke(mustExistCmd, args) 

334 self.assertNotEqual(result.exit_code, 0, clickResultMsg(result)) 

335 self.assertRegex(result.output, """['"]foo.txt['"] does not exist.""") 

336 

337 result = self.runner.invoke(mayExistCmd, args) 

338 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

339 

340 result = self.runner.invoke(mustNotExistCmd, args) 

341 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

342 

343 # isolated_filesystem runs in a temporary directory, when it is 

344 # removed everything inside will be removed. 

345 with open("foo.txt", "w") as _: 

346 result = self.runner.invoke(mustExistCmd, args) 

347 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

348 

349 result = self.runner.invoke(mayExistCmd, args) 

350 self.assertEqual(result.exit_code, 0, clickResultMsg(result)) 

351 

352 result = self.runner.invoke(mustNotExistCmd, args) 

353 self.assertNotEqual(result.exit_code, 0, clickResultMsg(result)) 

354 self.assertIn('"foo.txt" should not exist.', result.output) 

355 

356 

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

358 unittest.main()