Coverage for tests.py: 96%

195 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-02 17:29 +0000

1# Copyright (c) 2017, Emergence by Design Inc. 

2 

3import argparse 

4import os 

5import sys 

6from collections import OrderedDict 

7from functools import partial 

8from io import StringIO 

9from unittest import TestCase 

10from unittest import skipUnless 

11 

12from colors import bold 

13from colors import color 

14from colors import strip_color 

15from colors import underline 

16 

17from argparse_color_formatter import ColorHelpFormatter 

18from argparse_color_formatter import ColorTextWrapper 

19 

20 

21try: 

22 from contextlib import redirect_stderr 

23 from contextlib import redirect_stdout 

24except ImportError: 

25 import contextlib 

26 

27 @contextlib.contextmanager 

28 def redirect_stdout(target): 

29 original = sys.stdout 

30 sys.stdout = target 

31 yield 

32 sys.stdout = original 

33 

34 @contextlib.contextmanager 

35 def redirect_stderr(target): 

36 original = sys.stderr 

37 sys.stderr = target 

38 yield 

39 sys.stderr = original 

40 

41 

42colors = OrderedDict( 

43 ( 

44 ("red", partial(color, fg="red", style="bold")), 

45 ("orange", partial(color, fg="orange", style="bold")), 

46 ("yellow", partial(color, fg="yellow", style="bold")), 

47 ("green", partial(color, fg="green", style="bold")), 

48 ("blue", partial(color, fg="blue", style="bold")), 

49 ("indigo", partial(color, fg="indigo", style="bold")), 

50 ("violet", partial(color, fg="violet", style="bold")), 

51 ) 

52) 

53positions = ["first", "second", "third", "forth", "fifth", "sixth", "seventh"] 

54 

55color_kwargs = { 

56 "color": bold("color"), 

57 "typically": underline("typically"), 

58} 

59color_pos = OrderedDict((p, v(p)) for v, p in zip(colors.values(), positions)) 

60color_names = OrderedDict((k, v(k)) for k, v in colors.items()) 

61 

62if sys.version_info >= (3, 10): 62 ↛ 69line 62 didn't jump to line 69 because the condition on line 62 was always true

63 color_kwargs.update( 

64 { 

65 "options_string": "options", 

66 } 

67 ) 

68else: 

69 color_kwargs.update( 

70 { 

71 "options_string": "optional arguments", 

72 } 

73 ) 

74 

75 

76def rainbow_text(text): 

77 retval = [] 

78 colors_iter = iter(colors.values()) 

79 cur_color = next(colors_iter) 

80 for char in text: 

81 retval.append(cur_color(char)) 

82 try: 

83 cur_color = next(colors_iter) 

84 except StopIteration: 

85 colors_iter = iter(colors.values()) 

86 cur_color = next(colors_iter) 

87 return "".join(retval) 

88 

89 

90color_kwargs.update(color_pos) 

91color_kwargs.update(color_names) 

92color_kwargs.update( 

93 { 

94 "colorful": rainbow_text("colorful"), 

95 "rainbow_maker": rainbow_text("rainbow_maker"), 

96 "bow": rainbow_text("bow"), 

97 "red-orange-yellow-green-blue-indigo-violet": rainbow_text("red-orange-yellow-green-blue-indigo-violet"), 

98 } 

99) 

100 

101 

102def rainbow_maker_arg_help(color_name): 

103 return "{color} used when making rainbow, {typically} this would be {color_name}.".format( 

104 color=bold("color"), typically=underline("typically"), color_name=color_kwargs[color_name] 

105 ) 

106 

107 

108def rainbow_maker(args): 

109 parser = argparse.ArgumentParser( 

110 prog="{rainbow_maker}".format(**color_kwargs), 

111 usage="%(prog)s [-h] {first} {second} {third} {forth} {fifth} {sixth} {seventh}".format(**color_kwargs), 

112 epilog="This epilog has some {colorful} escapes in it as well and should not wrap on 80.".format( 

113 **color_kwargs 

114 ), 

115 description="This script is a test for {rainbow_maker}. This description consists of 140 chars." 

116 " It should be able to fit onto two 80 char lines.".format(**color_kwargs), 

117 formatter_class=ColorHelpFormatter, 

118 add_help=False, 

119 ) 

120 for arg_name, color_name in zip(color_pos.keys(), color_names.keys()): 

121 parser.add_argument(arg_name, default=color_name, help=rainbow_maker_arg_help(color_name)) 

122 parser.add_argument("-h", "--help", action="help", help="displays this {colorful} help text".format(**color_kwargs)) 

123 parser.parse_args(args) 

124 

125 

126def rainbow_maker_colored_metavar(args, *, longer_help=1): 

127 parser = argparse.ArgumentParser( 

128 prog="{rainbow_maker}".format(**color_kwargs), 

129 # with recent fixes, these will be colored as well if the metavar is colored, and wrapped properly. 

130 # so we don't need to do this ourselves anymore. 

131 # usage="%(prog)s [-h] {first} {second} {third} {forth} {fifth} {sixth} {seventh}".format(**color_kwargs), 

132 epilog="This epilog has some {colorful} escapes in it as well and should not wrap on 80.".format( 

133 **color_kwargs 

134 ), 

135 description="This script is a test for {rainbow_maker}. This description consists of 140 chars." 

136 " It should be able to fit onto two 80 char lines.".format(**color_kwargs), 

137 formatter_class=ColorHelpFormatter, 

138 add_help=False, 

139 ) 

140 for arg_name, color_name in zip(color_pos.keys(), color_names.keys()): 

141 parser.add_argument( 

142 color_name, 

143 metavar=color_kwargs[arg_name], 

144 default=color_name, 

145 help=rainbow_maker_arg_help(color_name) * longer_help, 

146 ) 

147 parser.add_argument("-h", "--help", action="help", help="displays this {colorful} help text".format(**color_kwargs)) 

148 parser.parse_args(args) 

149 

150 

151def rainbow_maker_auto_usage(args): 

152 parser = argparse.ArgumentParser( 

153 prog="{rainbow_maker}".format(**color_kwargs), 

154 epilog="This epilog has some {colorful} escapes in it as well and should not wrap on 80.".format( 

155 **color_kwargs 

156 ), 

157 description="This script is a test for {rainbow_maker}. This description consists of 140 chars." 

158 " It should be able to fit onto two 80 char lines.".format(**color_kwargs), 

159 formatter_class=ColorHelpFormatter, 

160 add_help=False, 

161 ) 

162 for arg_name, color_name in zip(color_pos.keys(), color_names.keys()): 

163 parser.add_argument(arg_name, default=color_name, help=rainbow_maker_arg_help(color_name)) 

164 parser.add_argument("-h", "--help", action="help", help="displays this {colorful} help text".format(**color_kwargs)) 

165 parser.parse_args(args) 

166 

167 

168def rainbow_maker_auto_usage_short_prog(args): 

169 parser = argparse.ArgumentParser( 

170 prog="{bow}".format(**color_kwargs), 

171 epilog="This epilog has some {colorful} escapes in it as well and should not wrap on 80.".format( 

172 **color_kwargs 

173 ), 

174 description="This script is a test for {rainbow_maker}. This description consists of 140 chars." 

175 " It should be able to fit onto two 80 char lines.".format(**color_kwargs), 

176 formatter_class=ColorHelpFormatter, 

177 add_help=False, 

178 ) 

179 for arg_name, color_name in zip(color_pos.keys(), color_names.keys()): 

180 parser.add_argument(arg_name, default=color_name, help=rainbow_maker_arg_help(color_name)) 

181 parser.add_argument("-h", "--help", action="help", help="displays this {colorful} help text".format(**color_kwargs)) 

182 parser.parse_args(args) 

183 

184 

185def rainbow_maker_auto_usage_long_prog(args): 

186 parser = argparse.ArgumentParser( 

187 prog="{red-orange-yellow-green-blue-indigo-violet}".format(**color_kwargs), 

188 epilog="This epilog has some {colorful} escapes in it as well and should not wrap on 80.".format( 

189 **color_kwargs 

190 ), 

191 description="This script is a test for {rainbow_maker}. This description consists of 140 chars." 

192 " It should be able to fit onto two 80 char lines.".format(**color_kwargs), 

193 formatter_class=ColorHelpFormatter, 

194 add_help=False, 

195 ) 

196 for arg_name, color_name in zip(color_pos.keys(), color_names.keys()): 

197 parser.add_argument(arg_name, default=color_name, help=rainbow_maker_arg_help(color_name)) 

198 parser.add_argument("-h", "--help", action="help", help="displays this {colorful} help text".format(**color_kwargs)) 

199 parser.parse_args(args) 

200 

201 

202def rainbow_maker_no_args(args): 

203 parser = argparse.ArgumentParser( 

204 prog="{rainbow_maker}".format(**color_kwargs), 

205 epilog="This epilog has some {colorful} escapes in it as well and should not wrap on 80.".format( 

206 **color_kwargs 

207 ), 

208 description="This script is a test for {rainbow_maker}. This description consists of 140 chars." 

209 " It should be able to fit onto two 80 char lines.".format(**color_kwargs), 

210 formatter_class=ColorHelpFormatter, 

211 add_help=False, 

212 ) 

213 parser.parse_args(args) 

214 

215 

216class TestColorArgsParserOutput(TestCase): 

217 maxDiff = None 

218 

219 def test_color_output_wrapped_as_expected(self): 

220 try: 

221 os.environ["COLUMNS"] = "80" 

222 out = StringIO() 

223 with redirect_stdout(out): 

224 self.assertRaises(SystemExit, rainbow_maker, ["-h"]) 

225 out.seek(0) 

226 self.assertEqual( 

227 out.read(), 

228 "usage: {rainbow_maker} [-h] {first} {second} {third} {forth} {fifth} {sixth} {seventh}\n" 

229 "\n" 

230 "This script is a test for {rainbow_maker}. This description consists of 140\n" 

231 "chars. It should be able to fit onto two 80 char lines.\n" 

232 "\n" 

233 "positional arguments:\n" 

234 " first {color} used when making rainbow, {typically} this would be {red}.\n" 

235 " second {color} used when making rainbow, {typically} this would be {orange}.\n" 

236 " third {color} used when making rainbow, {typically} this would be {yellow}.\n" 

237 " forth {color} used when making rainbow, {typically} this would be {green}.\n" 

238 " fifth {color} used when making rainbow, {typically} this would be {blue}.\n" 

239 " sixth {color} used when making rainbow, {typically} this would be {indigo}.\n" 

240 " seventh {color} used when making rainbow, {typically} this would be {violet}.\n" 

241 "\n" 

242 "{options_string}:\n" 

243 " -h, --help displays this {colorful} help text\n" 

244 "\n" 

245 "This epilog has some {colorful} escapes in it as well and should not wrap on 80.\n".format( 

246 **color_kwargs 

247 ), 

248 ) 

249 finally: 

250 del os.environ["COLUMNS"] 

251 

252 def test_color_output_wrapped_as_expected_small_width(self): 

253 try: 

254 os.environ["COLUMNS"] = "42" 

255 out = StringIO() 

256 with redirect_stdout(out): 

257 self.assertRaises(SystemExit, rainbow_maker, ["-h"]) 

258 out.seek(0) 

259 self.assertEqual( 

260 out.read(), 

261 # usage doesnt wrap for some reason when manually specified. 

262 # seems like a bug but leaving alone because seems out of scope re: colors. 

263 "usage: {rainbow_maker} [-h] {first} {second} {third} {forth} {fifth} {sixth} {seventh}\n" 

264 "\n" 

265 "This script is a test for {rainbow_maker}.\n" 

266 "This description consists of 140 chars.\n" 

267 "It should be able to fit onto two 80\n" 

268 "char lines.\n" 

269 "\n" 

270 "positional arguments:\n" 

271 " first {color} used when making\n" 

272 " rainbow, {typically} this\n" 

273 " would be {red}.\n" 

274 " second {color} used when making\n" 

275 " rainbow, {typically} this\n" 

276 " would be {orange}.\n" 

277 " third {color} used when making\n" 

278 " rainbow, {typically} this\n" 

279 " would be {yellow}.\n" 

280 " forth {color} used when making\n" 

281 " rainbow, {typically} this\n" 

282 " would be {green}.\n" 

283 " fifth {color} used when making\n" 

284 " rainbow, {typically} this\n" 

285 " would be {blue}.\n" 

286 " sixth {color} used when making\n" 

287 " rainbow, {typically} this\n" 

288 " would be {indigo}.\n" 

289 " seventh {color} used when making\n" 

290 " rainbow, {typically} this\n" 

291 " would be {violet}.\n" 

292 "\n" 

293 "{options_string}:\n" 

294 " -h, --help displays this {colorful}\n" 

295 " help text\n" 

296 "\n" 

297 "This epilog has some {colorful} escapes in\n" 

298 "it as well and should not wrap on 80.\n".format(**color_kwargs), 

299 ) 

300 finally: 

301 del os.environ["COLUMNS"] 

302 

303 def test_color_output_wrapped_as_expected_with_auto_usage(self): 

304 try: 

305 os.environ["COLUMNS"] = "80" 

306 out = StringIO() 

307 with redirect_stdout(out): 

308 self.assertRaises(SystemExit, rainbow_maker_auto_usage, ["-h"]) 

309 out.seek(0) 

310 self.assertEqual( 

311 out.read(), 

312 "usage: {rainbow_maker} [-h] first second third forth fifth sixth seventh\n" 

313 "\n" 

314 "This script is a test for {rainbow_maker}. This description consists of 140\n" 

315 "chars. It should be able to fit onto two 80 char lines.\n" 

316 "\n" 

317 "positional arguments:\n" 

318 " first {color} used when making rainbow, {typically} this would be {red}.\n" 

319 " second {color} used when making rainbow, {typically} this would be {orange}.\n" 

320 " third {color} used when making rainbow, {typically} this would be {yellow}.\n" 

321 " forth {color} used when making rainbow, {typically} this would be {green}.\n" 

322 " fifth {color} used when making rainbow, {typically} this would be {blue}.\n" 

323 " sixth {color} used when making rainbow, {typically} this would be {indigo}.\n" 

324 " seventh {color} used when making rainbow, {typically} this would be {violet}.\n" 

325 "\n" 

326 "{options_string}:\n" 

327 " -h, --help displays this {colorful} help text\n" 

328 "\n" 

329 "This epilog has some {colorful} escapes in it as well and should not wrap on 80.\n".format( 

330 **color_kwargs 

331 ), 

332 ) 

333 finally: 

334 del os.environ["COLUMNS"] 

335 

336 def test_color_output_wrapped_as_expected_with_auto_usage_small_width(self): 

337 try: 

338 os.environ["COLUMNS"] = "42" 

339 out = StringIO() 

340 with redirect_stdout(out): 

341 self.assertRaises(SystemExit, rainbow_maker_auto_usage, ["-h"]) 

342 out.seek(0) 

343 self.assertEqual( 

344 out.read(), 

345 "usage: {rainbow_maker} [-h]\n" 

346 " first second third\n" 

347 " forth fifth sixth\n" 

348 " seventh\n" 

349 "\n" 

350 "This script is a test for {rainbow_maker}.\n" 

351 "This description consists of 140 chars.\n" 

352 "It should be able to fit onto two 80\n" 

353 "char lines.\n" 

354 "\n" 

355 "positional arguments:\n" 

356 " first {color} used when making\n" 

357 " rainbow, {typically} this\n" 

358 " would be {red}.\n" 

359 " second {color} used when making\n" 

360 " rainbow, {typically} this\n" 

361 " would be {orange}.\n" 

362 " third {color} used when making\n" 

363 " rainbow, {typically} this\n" 

364 " would be {yellow}.\n" 

365 " forth {color} used when making\n" 

366 " rainbow, {typically} this\n" 

367 " would be {green}.\n" 

368 " fifth {color} used when making\n" 

369 " rainbow, {typically} this\n" 

370 " would be {blue}.\n" 

371 " sixth {color} used when making\n" 

372 " rainbow, {typically} this\n" 

373 " would be {indigo}.\n" 

374 " seventh {color} used when making\n" 

375 " rainbow, {typically} this\n" 

376 " would be {violet}.\n" 

377 "\n" 

378 "{options_string}:\n" 

379 " -h, --help displays this {colorful}\n" 

380 " help text\n" 

381 "\n" 

382 "This epilog has some {colorful} escapes in\n" 

383 "it as well and should not wrap on 80.\n".format(**color_kwargs), 

384 ) 

385 finally: 

386 del os.environ["COLUMNS"] 

387 

388 def test_color_output_wrapped_as_expected_with_auto_usage_short_prog_small_width(self): 

389 try: 

390 os.environ["COLUMNS"] = "42" 

391 out = StringIO() 

392 with redirect_stdout(out): 

393 self.assertRaises(SystemExit, rainbow_maker_auto_usage_short_prog, ["-h"]) 

394 out.seek(0) 

395 self.assertEqual( 

396 out.read(), 

397 "usage: {bow} [-h]\n" 

398 " first second third forth\n" 

399 " fifth sixth seventh\n" 

400 "\n" 

401 "This script is a test for {rainbow_maker}.\n" 

402 "This description consists of 140 chars.\n" 

403 "It should be able to fit onto two 80\n" 

404 "char lines.\n" 

405 "\n" 

406 "positional arguments:\n" 

407 " first {color} used when making\n" 

408 " rainbow, {typically} this\n" 

409 " would be {red}.\n" 

410 " second {color} used when making\n" 

411 " rainbow, {typically} this\n" 

412 " would be {orange}.\n" 

413 " third {color} used when making\n" 

414 " rainbow, {typically} this\n" 

415 " would be {yellow}.\n" 

416 " forth {color} used when making\n" 

417 " rainbow, {typically} this\n" 

418 " would be {green}.\n" 

419 " fifth {color} used when making\n" 

420 " rainbow, {typically} this\n" 

421 " would be {blue}.\n" 

422 " sixth {color} used when making\n" 

423 " rainbow, {typically} this\n" 

424 " would be {indigo}.\n" 

425 " seventh {color} used when making\n" 

426 " rainbow, {typically} this\n" 

427 " would be {violet}.\n" 

428 "\n" 

429 "{options_string}:\n" 

430 " -h, --help displays this {colorful}\n" 

431 " help text\n" 

432 "\n" 

433 "This epilog has some {colorful} escapes in\n" 

434 "it as well and should not wrap on 80.\n".format(**color_kwargs), 

435 ) 

436 finally: 

437 del os.environ["COLUMNS"] 

438 

439 def test_color_output_wrapped_as_expected_with_auto_usage_long_prog_small_width(self): 

440 try: 

441 os.environ["COLUMNS"] = "42" 

442 out = StringIO() 

443 with redirect_stdout(out): 

444 self.assertRaises(SystemExit, rainbow_maker_auto_usage_long_prog, ["-h"]) 

445 out.seek(0) 

446 self.assertEqual( 

447 out.read(), 

448 "usage: {red-orange-yellow-green-blue-indigo-violet}\n" 

449 " [-h]\n" 

450 " first second third forth fifth\n" 

451 " sixth seventh\n" 

452 "\n" 

453 "This script is a test for {rainbow_maker}.\n" 

454 "This description consists of 140 chars.\n" 

455 "It should be able to fit onto two 80\n" 

456 "char lines.\n" 

457 "\n" 

458 "positional arguments:\n" 

459 " first {color} used when making\n" 

460 " rainbow, {typically} this\n" 

461 " would be {red}.\n" 

462 " second {color} used when making\n" 

463 " rainbow, {typically} this\n" 

464 " would be {orange}.\n" 

465 " third {color} used when making\n" 

466 " rainbow, {typically} this\n" 

467 " would be {yellow}.\n" 

468 " forth {color} used when making\n" 

469 " rainbow, {typically} this\n" 

470 " would be {green}.\n" 

471 " fifth {color} used when making\n" 

472 " rainbow, {typically} this\n" 

473 " would be {blue}.\n" 

474 " sixth {color} used when making\n" 

475 " rainbow, {typically} this\n" 

476 " would be {indigo}.\n" 

477 " seventh {color} used when making\n" 

478 " rainbow, {typically} this\n" 

479 " would be {violet}.\n" 

480 "\n" 

481 "{options_string}:\n" 

482 " -h, --help displays this {colorful}\n" 

483 " help text\n" 

484 "\n" 

485 "This epilog has some {colorful} escapes in\n" 

486 "it as well and should not wrap on 80.\n".format(**color_kwargs), 

487 ) 

488 finally: 

489 del os.environ["COLUMNS"] 

490 

491 def test_color_output_wrapped_as_expected_with_no_args(self): 

492 out = StringIO() 

493 with redirect_stderr(out): 

494 self.assertRaises(SystemExit, rainbow_maker_no_args, ["--bad"]) 

495 out.seek(0) 

496 self.assertEqual( 

497 out.read(), 

498 "usage: {rainbow_maker}\n{rainbow_maker}: error: unrecognized arguments: --bad\n".format(**color_kwargs), 

499 ) 

500 

501 def test_color_output_with_long_help(self): 

502 try: 

503 os.environ["COLUMNS"] = "42" 

504 out = StringIO() 

505 with redirect_stdout(out): 

506 self.assertRaises(SystemExit, partial(rainbow_maker_colored_metavar, longer_help=2), ["-h"]) 

507 out.seek(0) 

508 self.assertEqual( 

509 out.read(), 

510 "usage: {rainbow_maker} [-h]\n" 

511 " {first} {second} {third}\n" 

512 " {forth} {fifth} {sixth}\n" 

513 " {seventh}\n" 

514 "\n" 

515 "This script is a test for {rainbow_maker}.\n" 

516 "This description consists of 140 chars.\n" 

517 "It should be able to fit onto two 80\n" 

518 "char lines.\n" 

519 "\n" 

520 "positional arguments:\n" 

521 " {first} {color} used when making\n" 

522 " rainbow, {typically} this\n" 

523 " would be {red}.{color} used\n" 

524 " when making rainbow,\n" 

525 " {typically} this would be\n" 

526 " {red}.\n" 

527 " {second} {color} used when making\n" 

528 " rainbow, {typically} this\n" 

529 " would be {orange}.{color} used\n" 

530 " when making rainbow,\n" 

531 " {typically} this would be\n" 

532 " {orange}.\n" 

533 " {third} {color} used when making\n" 

534 " rainbow, {typically} this\n" 

535 " would be {yellow}.{color} used\n" 

536 " when making rainbow,\n" 

537 " {typically} this would be\n" 

538 " {yellow}.\n" 

539 " {forth} {color} used when making\n" 

540 " rainbow, {typically} this\n" 

541 " would be {green}.{color} used\n" 

542 " when making rainbow,\n" 

543 " {typically} this would be\n" 

544 " {green}.\n" 

545 " {fifth} {color} used when making\n" 

546 " rainbow, {typically} this\n" 

547 " would be {blue}.{color} used\n" 

548 " when making rainbow,\n" 

549 " {typically} this would be\n" 

550 " {blue}.\n" 

551 " {sixth} {color} used when making\n" 

552 " rainbow, {typically} this\n" 

553 " would be {indigo}.{color} used\n" 

554 " when making rainbow,\n" 

555 " {typically} this would be\n" 

556 " {indigo}.\n" 

557 " {seventh} {color} used when making\n" 

558 " rainbow, {typically} this\n" 

559 " would be {violet}.{color} used\n" 

560 " when making rainbow,\n" 

561 " {typically} this would be\n" 

562 " {violet}.\n" 

563 "\n" 

564 "{options_string}:\n" 

565 " -h, --help displays this {colorful}\n" 

566 " help text\n" 

567 "\n" 

568 "This epilog has some {colorful} escapes in\n" 

569 "it as well and should not wrap on 80.\n".format(**color_kwargs), 

570 ) 

571 finally: 

572 del os.environ["COLUMNS"] 

573 

574 @skipUnless(sys.version_info >= (3, 14), "argparse added native colors in Python 3.14") 

575 def test_python314_native_colors_and_application_colors_wrap_correctly(self): 

576 old_columns = os.environ.get("COLUMNS") 

577 old_python_colors = os.environ.get("PYTHON_COLORS") 

578 try: 

579 os.environ["COLUMNS"] = "32" 

580 os.environ["PYTHON_COLORS"] = "1" 

581 colored_word = color("ABCDEFGHIJ", fg="red") 

582 parser = argparse.ArgumentParser( 

583 prog="rainbow-tool", 

584 description=f"prefix {colored_word} suffix words that should wrap cleanly", 

585 formatter_class=ColorHelpFormatter, 

586 ) 

587 parser.add_argument( 

588 "--example", 

589 metavar=colored_word, 

590 help=f"prefix {colored_word} suffix words that should wrap cleanly", 

591 ) 

592 

593 output = parser.format_help() 

594 self.assertIn("\x1b[", output) 

595 plain_output = strip_color(output) 

596 self.assertIn( 

597 "usage: rainbow-tool [-h]\n [--example ABCDEFGHIJ]\n", 

598 plain_output, 

599 ) 

600 self.assertIn( 

601 "prefix ABCDEFGHIJ suffix words\nthat should wrap cleanly\n", 

602 plain_output, 

603 ) 

604 self.assertIn( 

605 " prefix ABCDEFGHIJ\n suffix words that\n should wrap cleanly\n", 

606 plain_output, 

607 ) 

608 finally: 

609 if old_columns is None: 609 ↛ 612line 609 didn't jump to line 612 because the condition on line 609 was always true

610 del os.environ["COLUMNS"] 

611 else: 

612 os.environ["COLUMNS"] = old_columns 

613 if old_python_colors is None: 613 ↛ 616line 613 didn't jump to line 616 because the condition on line 613 was always true

614 del os.environ["PYTHON_COLORS"] 

615 else: 

616 os.environ["PYTHON_COLORS"] = old_python_colors 

617 

618 

619class TestColorTextWrapper(TestCase): 

620 def test_bad_width_error(self): 

621 ctw = ColorTextWrapper(width=-1) 

622 self.assertRaisesRegex( 

623 ValueError, r"invalid width -1 \(must be > 0\)", lambda: ctw.wrap("This is some text to wrap.") 

624 ) 

625 

626 def test_starting_whitespace(self): 

627 ctw = ColorTextWrapper(width=20) 

628 self.assertEqual( 

629 ctw.wrap(" 01234 56789 01234 56789 01234 56789 01234 56789"), 

630 [" 01234 56789 01234", "56789 01234 56789", "01234 56789"], 

631 ) 

632 

633 def test_max_lines_and_placeholder(self): 

634 ctw = ColorTextWrapper(width=10, max_lines=2, placeholder="**" * 10) 

635 self.assertRaisesRegex( 

636 ValueError, 

637 r"placeholder too large for max width", 

638 lambda: ctw.wrap("01234 56789 01234 56789 01234 56789 01234 56789"), 

639 ) 

640 

641 def test_max_lines_and_indent(self): 

642 ctw = ColorTextWrapper(width=20, max_lines=2, initial_indent=" ") 

643 self.assertEqual( 

644 ctw.wrap("01234 56789 01234 56789 01234 56789 01234 56789"), [" 01234 56789 01234", "56789 01234 [...]"] 

645 ) 

646 

647 def test_max_lines_and_subsequence_indent(self): 

648 ctw = ColorTextWrapper(width=20, max_lines=0, initial_indent=" ", subsequent_indent=" ") 

649 self.assertEqual(ctw.wrap("01234 56789 01234 56789 01234 56789 01234 56789"), [" 01234 56789 [...]"]) 

650 

651 def test_too_big(self): 

652 ctw = ColorTextWrapper(width=10) 

653 self.assertEqual( 

654 ctw.wrap("0123456789 0123456789 01234567890123456789"), 

655 ["0123456789", "0123456789", "0123456789", "0123456789"], 

656 ) 

657 

658 def test_placeholder_edge_case(self): 

659 ctw = ColorTextWrapper(width=4, max_lines=1, placeholder="***") 

660 self.assertEqual(ctw.wrap("0123456789"), ["***"]) 

661 

662 def test_placeholder_edge_case_2(self): 

663 ctw = ColorTextWrapper(width=5, max_lines=2, placeholder="****") 

664 self.assertEqual(ctw.wrap("0123456789 " * 2), ["01234", "****"]) 

665 

666 

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

668 rainbow_maker_colored_metavar(None, longer_help=2)