Coverage for cogapp/test_cogapp.py: 29.19%

905 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-13 08:29 -0400

1"""Test cogapp.""" 

2 

3import io 

4import os 

5import os.path 

6import random 

7import re 

8import shutil 

9import stat 

10import sys 

11import tempfile 

12import threading 

13from unittest import TestCase 

14 

15from .cogapp import Cog, CogOptions, CogGenerator 

16from .cogapp import CogError, CogUsageError, CogGeneratedError, CogUserException 

17from .cogapp import usage, __version__, main 

18from .hashhandler import HashHandler 

19from .makefiles import make_files 

20from .whiteutils import reindent_block 

21 

22 

23class CogTestsInMemory(TestCase): 

24 """Test cases for cogapp.Cog()""" 

25 

26 def test_no_cog(self): 

27 strings = [ 

28 "", 

29 " ", 

30 " \t \t \tx", 

31 "hello", 

32 "the cat\nin the\nhat.", 

33 "Horton\n\tHears A\n\t\tWho", 

34 ] 

35 for s in strings: 

36 self.assertEqual(Cog().process_string(s), s) 

37 

38 def test_simple(self): 

39 infile = """\ 

40 Some text. 

41 //[[[cog 

42 import cog 

43 cog.outl("This is line one\\n") 

44 cog.outl("This is line two") 

45 //]]] 

46 gobbledegook. 

47 //[[[end]]] 

48 epilogue. 

49 """ 

50 

51 outfile = """\ 

52 Some text. 

53 //[[[cog 

54 import cog 

55 cog.outl("This is line one\\n") 

56 cog.outl("This is line two") 

57 //]]] 

58 This is line one 

59 

60 This is line two 

61 //[[[end]]] 

62 epilogue. 

63 """ 

64 

65 self.assertEqual(Cog().process_string(infile), outfile) 

66 

67 def test_empty_cog(self): 

68 # The cog clause can be totally empty. Not sure why you'd want it, 

69 # but it works. 

70 infile = """\ 

71 hello 

72 //[[[cog 

73 //]]] 

74 //[[[end]]] 

75 goodbye 

76 """ 

77 

78 infile = reindent_block(infile) 

79 self.assertEqual(Cog().process_string(infile), infile) 

80 

81 def test_multiple_cogs(self): 

82 # One file can have many cog chunks, even abutting each other. 

83 infile = """\ 

84 //[[[cog 

85 cog.out("chunk1") 

86 //]]] 

87 chunk1 

88 //[[[end]]] 

89 //[[[cog 

90 cog.out("chunk2") 

91 //]]] 

92 chunk2 

93 //[[[end]]] 

94 between chunks 

95 //[[[cog 

96 cog.out("chunk3") 

97 //]]] 

98 chunk3 

99 //[[[end]]] 

100 """ 

101 

102 infile = reindent_block(infile) 

103 self.assertEqual(Cog().process_string(infile), infile) 

104 

105 def test_trim_blank_lines(self): 

106 infile = """\ 

107 //[[[cog 

108 cog.out("This is line one\\n", trimblanklines=True) 

109 cog.out(''' 

110 This is line two 

111 ''', dedent=True, trimblanklines=True) 

112 cog.outl("This is line three", trimblanklines=True) 

113 //]]] 

114 This is line one 

115 This is line two 

116 This is line three 

117 //[[[end]]] 

118 """ 

119 

120 infile = reindent_block(infile) 

121 self.assertEqual(Cog().process_string(infile), infile) 

122 

123 def test_trim_empty_blank_lines(self): 

124 infile = """\ 

125 //[[[cog 

126 cog.out("This is line one\\n", trimblanklines=True) 

127 cog.out(''' 

128 This is line two 

129 ''', dedent=True, trimblanklines=True) 

130 cog.out('', dedent=True, trimblanklines=True) 

131 cog.outl("This is line three", trimblanklines=True) 

132 //]]] 

133 This is line one 

134 This is line two 

135 This is line three 

136 //[[[end]]] 

137 """ 

138 

139 infile = reindent_block(infile) 

140 self.assertEqual(Cog().process_string(infile), infile) 

141 

142 def test_trim_blank_lines_with_last_partial(self): 

143 infile = """\ 

144 //[[[cog 

145 cog.out("This is line one\\n", trimblanklines=True) 

146 cog.out("\\nLine two\\nLine three", trimblanklines=True) 

147 //]]] 

148 This is line one 

149 Line two 

150 Line three 

151 //[[[end]]] 

152 """ 

153 

154 infile = reindent_block(infile) 

155 self.assertEqual(Cog().process_string(infile), infile) 

156 

157 def test_cog_out_dedent(self): 

158 infile = """\ 

159 //[[[cog 

160 cog.out("This is the first line\\n") 

161 cog.out(''' 

162 This is dedent=True 1 

163 This is dedent=True 2 

164 ''', dedent=True, trimblanklines=True) 

165 cog.out(''' 

166 This is dedent=False 1 

167 This is dedent=False 2 

168 ''', dedent=False, trimblanklines=True) 

169 cog.out(''' 

170 This is dedent=default 1 

171 This is dedent=default 2 

172 ''', trimblanklines=True) 

173 cog.out("This is the last line\\n") 

174 //]]] 

175 This is the first line 

176 This is dedent=True 1 

177 This is dedent=True 2 

178 This is dedent=False 1 

179 This is dedent=False 2 

180 This is dedent=default 1 

181 This is dedent=default 2 

182 This is the last line 

183 //[[[end]]] 

184 """ 

185 

186 infile = reindent_block(infile) 

187 self.assertEqual(Cog().process_string(infile), infile) 

188 

189 def test22_end_of_line(self): 

190 # In Python 2.2, this cog file was not parsing because the 

191 # last line is indented but didn't end with a newline. 

192 infile = """\ 

193 //[[[cog 

194 import cog 

195 for i in range(3): 

196 cog.out("%d\\n" % i) 

197 //]]] 

198 0 

199 1 

200 2 

201 //[[[end]]] 

202 """ 

203 

204 infile = reindent_block(infile) 

205 self.assertEqual(Cog().process_string(infile), infile) 

206 

207 def test_indented_code(self): 

208 infile = """\ 

209 first line 

210 [[[cog 

211 import cog 

212 for i in range(3): 

213 cog.out("xx%d\\n" % i) 

214 ]]] 

215 xx0 

216 xx1 

217 xx2 

218 [[[end]]] 

219 last line 

220 """ 

221 

222 infile = reindent_block(infile) 

223 self.assertEqual(Cog().process_string(infile), infile) 

224 

225 def test_prefixed_code(self): 

226 infile = """\ 

227 --[[[cog 

228 --import cog 

229 --for i in range(3): 

230 -- cog.out("xx%d\\n" % i) 

231 --]]] 

232 xx0 

233 xx1 

234 xx2 

235 --[[[end]]] 

236 """ 

237 

238 infile = reindent_block(infile) 

239 self.assertEqual(Cog().process_string(infile), infile) 

240 

241 def test_prefixed_indented_code(self): 

242 infile = """\ 

243 prologue 

244 --[[[cog 

245 -- import cog 

246 -- for i in range(3): 

247 -- cog.out("xy%d\\n" % i) 

248 --]]] 

249 xy0 

250 xy1 

251 xy2 

252 --[[[end]]] 

253 """ 

254 

255 infile = reindent_block(infile) 

256 self.assertEqual(Cog().process_string(infile), infile) 

257 

258 def test_bogus_prefix_match(self): 

259 infile = """\ 

260 prologue 

261 #[[[cog 

262 import cog 

263 # This comment should not be clobbered by removing the pound sign. 

264 for i in range(3): 

265 cog.out("xy%d\\n" % i) 

266 #]]] 

267 xy0 

268 xy1 

269 xy2 

270 #[[[end]]] 

271 """ 

272 

273 infile = reindent_block(infile) 

274 self.assertEqual(Cog().process_string(infile), infile) 

275 

276 def test_no_final_newline(self): 

277 # If the cog'ed output has no final newline, 

278 # it shouldn't eat up the cog terminator. 

279 infile = """\ 

280 prologue 

281 [[[cog 

282 import cog 

283 for i in range(3): 

284 cog.out("%d" % i) 

285 ]]] 

286 012 

287 [[[end]]] 

288 epilogue 

289 """ 

290 

291 infile = reindent_block(infile) 

292 self.assertEqual(Cog().process_string(infile), infile) 

293 

294 def test_no_output_at_all(self): 

295 # If there is absolutely no cog output, that's ok. 

296 infile = """\ 

297 prologue 

298 [[[cog 

299 i = 1 

300 ]]] 

301 [[[end]]] 

302 epilogue 

303 """ 

304 

305 infile = reindent_block(infile) 

306 self.assertEqual(Cog().process_string(infile), infile) 

307 

308 def test_purely_blank_line(self): 

309 # If there is a blank line in the cog code with no whitespace 

310 # prefix, that should be OK. 

311 

312 infile = """\ 

313 prologue 

314 [[[cog 

315 import sys 

316 cog.out("Hello") 

317 $ 

318 cog.out("There") 

319 ]]] 

320 HelloThere 

321 [[[end]]] 

322 epilogue 

323 """ 

324 

325 infile = reindent_block(infile.replace("$", "")) 

326 self.assertEqual(Cog().process_string(infile), infile) 

327 

328 def test_empty_outl(self): 

329 # Alexander Belchenko suggested the string argument to outl should 

330 # be optional. Does it work? 

331 

332 infile = """\ 

333 prologue 

334 [[[cog 

335 cog.outl("x") 

336 cog.outl() 

337 cog.outl("y") 

338 cog.out() # Also optional, a complete no-op. 

339 cog.outl(trimblanklines=True) 

340 cog.outl("z") 

341 ]]] 

342 x 

343 

344 y 

345 

346 z 

347 [[[end]]] 

348 epilogue 

349 """ 

350 

351 infile = reindent_block(infile) 

352 self.assertEqual(Cog().process_string(infile), infile) 

353 

354 def test_first_line_num(self): 

355 infile = """\ 

356 fooey 

357 [[[cog 

358 cog.outl("started at line number %d" % cog.firstLineNum) 

359 ]]] 

360 started at line number 2 

361 [[[end]]] 

362 blah blah 

363 [[[cog 

364 cog.outl("and again at line %d" % cog.firstLineNum) 

365 ]]] 

366 and again at line 8 

367 [[[end]]] 

368 """ 

369 

370 infile = reindent_block(infile) 

371 self.assertEqual(Cog().process_string(infile), infile) 

372 

373 def test_compact_one_line_code(self): 

374 infile = """\ 

375 first line 

376 hey: [[[cog cog.outl("hello %d" % (3*3*3*3)) ]]] looky! 

377 get rid of this! 

378 [[[end]]] 

379 last line 

380 """ 

381 

382 outfile = """\ 

383 first line 

384 hey: [[[cog cog.outl("hello %d" % (3*3*3*3)) ]]] looky! 

385 hello 81 

386 [[[end]]] 

387 last line 

388 """ 

389 

390 infile = reindent_block(infile) 

391 self.assertEqual(Cog().process_string(infile), reindent_block(outfile)) 

392 

393 def test_inside_out_compact(self): 

394 infile = """\ 

395 first line 

396 hey?: ]]] what is this? [[[cog strange! 

397 get rid of this! 

398 [[[end]]] 

399 last line 

400 """ 

401 with self.assertRaisesRegex( 

402 CogError, r"^infile.txt\(2\): Cog code markers inverted$" 

403 ): 

404 Cog().process_string(reindent_block(infile), "infile.txt") 

405 

406 def test_sharing_globals(self): 

407 infile = """\ 

408 first line 

409 hey: [[[cog s="hey there" ]]] looky! 

410 [[[end]]] 

411 more literal junk. 

412 [[[cog cog.outl(s) ]]] 

413 [[[end]]] 

414 last line 

415 """ 

416 

417 outfile = """\ 

418 first line 

419 hey: [[[cog s="hey there" ]]] looky! 

420 [[[end]]] 

421 more literal junk. 

422 [[[cog cog.outl(s) ]]] 

423 hey there 

424 [[[end]]] 

425 last line 

426 """ 

427 

428 infile = reindent_block(infile) 

429 self.assertEqual(Cog().process_string(infile), reindent_block(outfile)) 

430 

431 def test_assert_in_cog_code(self): 

432 # Check that we can test assertions in cog code in the test framework. 

433 infile = """\ 

434 [[[cog 

435 assert 1 == 2, "Oops" 

436 ]]] 

437 [[[end]]] 

438 """ 

439 infile = reindent_block(infile) 

440 with self.assertRaisesRegex(CogUserException, "AssertionError: Oops"): 

441 Cog().process_string(infile) 

442 

443 def test_cog_previous(self): 

444 # Check that we can access the previous run's output. 

445 infile = """\ 

446 [[[cog 

447 assert cog.previous == "Hello there!\\n", "WTF??" 

448 cog.out(cog.previous) 

449 cog.outl("Ran again!") 

450 ]]] 

451 Hello there! 

452 [[[end]]] 

453 """ 

454 

455 outfile = """\ 

456 [[[cog 

457 assert cog.previous == "Hello there!\\n", "WTF??" 

458 cog.out(cog.previous) 

459 cog.outl("Ran again!") 

460 ]]] 

461 Hello there! 

462 Ran again! 

463 [[[end]]] 

464 """ 

465 

466 infile = reindent_block(infile) 

467 self.assertEqual(Cog().process_string(infile), reindent_block(outfile)) 

468 

469 

470class CogOptionsTests(TestCase): 

471 """Test the CogOptions class.""" 

472 

473 def test_equality(self): 

474 o = CogOptions() 

475 p = CogOptions() 

476 self.assertEqual(o, p) 

477 o.parse_args(["-r"]) 

478 self.assertNotEqual(o, p) 

479 p.parse_args(["-r"]) 

480 self.assertEqual(o, p) 

481 

482 def test_cloning(self): 

483 o = CogOptions() 

484 o.parse_args(["-I", "fooey", "-I", "booey", "-s", " /*x*/"]) 

485 p = o.clone() 

486 self.assertEqual(o, p) 

487 p.parse_args(["-I", "huey", "-D", "foo=quux"]) 

488 self.assertNotEqual(o, p) 

489 q = CogOptions() 

490 q.parse_args( 

491 [ 

492 "-I", 

493 "fooey", 

494 "-I", 

495 "booey", 

496 "-s", 

497 " /*x*/", 

498 "-I", 

499 "huey", 

500 "-D", 

501 "foo=quux", 

502 ] 

503 ) 

504 self.assertEqual(p, q) 

505 

506 def test_combining_flags(self): 

507 # Single-character flags can be combined. 

508 o = CogOptions() 

509 o.parse_args(["-e", "-r", "-z"]) 

510 p = CogOptions() 

511 p.parse_args(["-erz"]) 

512 self.assertEqual(o, p) 

513 

514 def test_markers(self): 

515 o = CogOptions() 

516 o._parse_markers("a b c") 

517 self.assertEqual("a", o.begin_spec) 

518 self.assertEqual("b", o.end_spec) 

519 self.assertEqual("c", o.end_output) 

520 

521 def test_markers_switch(self): 

522 o = CogOptions() 

523 o.parse_args(["--markers", "a b c"]) 

524 self.assertEqual("a", o.begin_spec) 

525 self.assertEqual("b", o.end_spec) 

526 self.assertEqual("c", o.end_output) 

527 

528 

529class FileStructureTests(TestCase): 

530 """Test that we're properly strict about the structure of files.""" 

531 

532 def is_bad(self, infile, msg=None): 

533 infile = reindent_block(infile) 

534 with self.assertRaisesRegex(CogError, "^" + re.escape(msg) + "$"): 

535 Cog().process_string(infile, "infile.txt") 

536 

537 def test_begin_no_end(self): 

538 infile = """\ 

539 Fooey 

540 #[[[cog 

541 cog.outl('hello') 

542 """ 

543 self.is_bad(infile, "infile.txt(2): Cog block begun but never ended.") 

544 

545 def test_no_eoo(self): 

546 infile = """\ 

547 Fooey 

548 #[[[cog 

549 cog.outl('hello') 

550 #]]] 

551 """ 

552 self.is_bad(infile, "infile.txt(4): Missing '[[[end]]]' before end of file.") 

553 

554 infile2 = """\ 

555 Fooey 

556 #[[[cog 

557 cog.outl('hello') 

558 #]]] 

559 #[[[cog 

560 cog.outl('goodbye') 

561 #]]] 

562 """ 

563 self.is_bad(infile2, "infile.txt(5): Unexpected '[[[cog'") 

564 

565 def test_start_with_end(self): 

566 infile = """\ 

567 #]]] 

568 """ 

569 self.is_bad(infile, "infile.txt(1): Unexpected ']]]'") 

570 

571 infile2 = """\ 

572 #[[[cog 

573 cog.outl('hello') 

574 #]]] 

575 #[[[end]]] 

576 #]]] 

577 """ 

578 self.is_bad(infile2, "infile.txt(5): Unexpected ']]]'") 

579 

580 def test_start_with_eoo(self): 

581 infile = """\ 

582 #[[[end]]] 

583 """ 

584 self.is_bad(infile, "infile.txt(1): Unexpected '[[[end]]]'") 

585 

586 infile2 = """\ 

587 #[[[cog 

588 cog.outl('hello') 

589 #]]] 

590 #[[[end]]] 

591 #[[[end]]] 

592 """ 

593 self.is_bad(infile2, "infile.txt(5): Unexpected '[[[end]]]'") 

594 

595 def test_no_end(self): 

596 infile = """\ 

597 #[[[cog 

598 cog.outl("hello") 

599 #[[[end]]] 

600 """ 

601 self.is_bad(infile, "infile.txt(3): Unexpected '[[[end]]]'") 

602 

603 infile2 = """\ 

604 #[[[cog 

605 cog.outl('hello') 

606 #]]] 

607 #[[[end]]] 

608 #[[[cog 

609 cog.outl("hello") 

610 #[[[end]]] 

611 """ 

612 self.is_bad(infile2, "infile.txt(7): Unexpected '[[[end]]]'") 

613 

614 def test_two_begins(self): 

615 infile = """\ 

616 #[[[cog 

617 #[[[cog 

618 cog.outl("hello") 

619 #]]] 

620 #[[[end]]] 

621 """ 

622 self.is_bad(infile, "infile.txt(2): Unexpected '[[[cog'") 

623 

624 infile2 = """\ 

625 #[[[cog 

626 cog.outl("hello") 

627 #]]] 

628 #[[[end]]] 

629 #[[[cog 

630 #[[[cog 

631 cog.outl("hello") 

632 #]]] 

633 #[[[end]]] 

634 """ 

635 self.is_bad(infile2, "infile.txt(6): Unexpected '[[[cog'") 

636 

637 def test_two_ends(self): 

638 infile = """\ 

639 #[[[cog 

640 cog.outl("hello") 

641 #]]] 

642 #]]] 

643 #[[[end]]] 

644 """ 

645 self.is_bad(infile, "infile.txt(4): Unexpected ']]]'") 

646 

647 infile2 = """\ 

648 #[[[cog 

649 cog.outl("hello") 

650 #]]] 

651 #[[[end]]] 

652 #[[[cog 

653 cog.outl("hello") 

654 #]]] 

655 #]]] 

656 #[[[end]]] 

657 """ 

658 self.is_bad(infile2, "infile.txt(8): Unexpected ']]]'") 

659 

660 

661class CogErrorTests(TestCase): 

662 """Test cases for cog.error().""" 

663 

664 def test_error_msg(self): 

665 infile = """\ 

666 [[[cog cog.error("This ain't right!")]]] 

667 [[[end]]] 

668 """ 

669 

670 infile = reindent_block(infile) 

671 with self.assertRaisesRegex(CogGeneratedError, "^This ain't right!$"): 

672 Cog().process_string(infile) 

673 

674 def test_error_no_msg(self): 

675 infile = """\ 

676 [[[cog cog.error()]]] 

677 [[[end]]] 

678 """ 

679 

680 infile = reindent_block(infile) 

681 with self.assertRaisesRegex( 

682 CogGeneratedError, "^Error raised by cog generator.$" 

683 ): 

684 Cog().process_string(infile) 

685 

686 def test_no_error_if_error_not_called(self): 

687 infile = """\ 

688 --[[[cog 

689 --import cog 

690 --for i in range(3): 

691 -- if i > 10: 

692 -- cog.error("Something is amiss!") 

693 -- cog.out("xx%d\\n" % i) 

694 --]]] 

695 xx0 

696 xx1 

697 xx2 

698 --[[[end]]] 

699 """ 

700 

701 infile = reindent_block(infile) 

702 self.assertEqual(Cog().process_string(infile), infile) 

703 

704 

705class CogGeneratorGetCodeTests(TestCase): 

706 """Tests for CogGenerator.getCode().""" 

707 

708 def setUp(self): 

709 # All tests get a generator to use, and short same-length names for 

710 # the functions we're going to use. 

711 self.gen = CogGenerator() 

712 self.m = self.gen.parse_marker 

713 self.parse_line = self.gen.parse_line 

714 

715 def test_empty(self): 

716 self.m("// [[[cog") 

717 self.m("// ]]]") 

718 self.assertEqual(self.gen.get_code(), "") 

719 

720 def test_simple(self): 

721 self.m("// [[[cog") 

722 self.parse_line(' print "hello"') 

723 self.parse_line(' print "bye"') 

724 self.m("// ]]]") 

725 self.assertEqual(self.gen.get_code(), 'print "hello"\nprint "bye"') 

726 

727 def test_compressed1(self): 

728 # For a while, I supported compressed code blocks, but no longer. 

729 self.m('// [[[cog: print """') 

730 self.parse_line("// hello") 

731 self.parse_line("// bye") 

732 self.m('// """)]]]') 

733 self.assertEqual(self.gen.get_code(), "hello\nbye") 

734 

735 def test_compressed2(self): 

736 # For a while, I supported compressed code blocks, but no longer. 

737 self.m('// [[[cog: print """') 

738 self.parse_line("hello") 

739 self.parse_line("bye") 

740 self.m('// """)]]]') 

741 self.assertEqual(self.gen.get_code(), "hello\nbye") 

742 

743 def test_compressed3(self): 

744 # For a while, I supported compressed code blocks, but no longer. 

745 self.m("// [[[cog") 

746 self.parse_line('print """hello') 

747 self.parse_line("bye") 

748 self.m('// """)]]]') 

749 self.assertEqual(self.gen.get_code(), 'print """hello\nbye') 

750 

751 def test_compressed4(self): 

752 # For a while, I supported compressed code blocks, but no longer. 

753 self.m('// [[[cog: print """') 

754 self.parse_line("hello") 

755 self.parse_line('bye""")') 

756 self.m("// ]]]") 

757 self.assertEqual(self.gen.get_code(), 'hello\nbye""")') 

758 

759 def test_no_common_prefix_for_markers(self): 

760 # It's important to be able to use #if 0 to hide lines from a 

761 # C++ compiler. 

762 self.m("#if 0 //[[[cog") 

763 self.parse_line("\timport cog, sys") 

764 self.parse_line("") 

765 self.parse_line("\tprint sys.argv") 

766 self.m("#endif //]]]") 

767 self.assertEqual(self.gen.get_code(), "import cog, sys\n\nprint sys.argv") 

768 

769 

770class TestCaseWithTempDir(TestCase): 

771 def new_cog(self): 

772 """Initialize the cog members for another run.""" 

773 # Create a cog engine, and catch its output. 

774 self.cog = Cog() 

775 self.output = io.StringIO() 

776 self.cog.set_output(stdout=self.output, stderr=self.output) 

777 

778 def setUp(self): 

779 # Create a temporary directory. 

780 self.tempdir = os.path.join( 

781 tempfile.gettempdir(), "testcog_tempdir_" + str(random.random())[2:] 

782 ) 

783 os.mkdir(self.tempdir) 

784 self.olddir = os.getcwd() 

785 os.chdir(self.tempdir) 

786 self.new_cog() 

787 

788 def tearDown(self): 

789 os.chdir(self.olddir) 

790 # Get rid of the temporary directory. 

791 shutil.rmtree(self.tempdir) 

792 

793 def assertFilesSame(self, file_name1, file_name2): 

794 with open(os.path.join(self.tempdir, file_name1), "rb") as f1: 

795 text1 = f1.read() 

796 with open(os.path.join(self.tempdir, file_name2), "rb") as f2: 

797 text2 = f2.read() 

798 self.assertEqual(text1, text2) 

799 

800 def assertFileContent(self, fname, content): 

801 absname = os.path.join(self.tempdir, fname) 

802 with open(absname, "rb") as f: 

803 file_content = f.read() 

804 self.assertEqual(file_content, content.encode("utf-8")) 

805 

806 

807class ArgumentHandlingTests(TestCaseWithTempDir): 

808 def test_argument_failure(self): 

809 # Return value 2 means usage problem. 

810 self.assertEqual(self.cog.main(["argv0", "-j"]), 2) 

811 output = self.output.getvalue() 

812 self.assertIn("option -j not recognized", output) 

813 with self.assertRaisesRegex(CogUsageError, r"^No files to process$"): 

814 self.cog.callable_main(["argv0"]) 

815 with self.assertRaisesRegex(CogUsageError, r"^option -j not recognized$"): 

816 self.cog.callable_main(["argv0", "-j"]) 

817 

818 def test_no_dash_o_and_at_file(self): 

819 make_files({"cogfiles.txt": "# Please run cog"}) 

820 with self.assertRaisesRegex(CogUsageError, r"^Can't use -o with @file$"): 

821 self.cog.callable_main(["argv0", "-o", "foo", "@cogfiles.txt"]) 

822 

823 def test_no_dash_o_and_amp_file(self): 

824 make_files({"cogfiles.txt": "# Please run cog"}) 

825 with self.assertRaisesRegex(CogUsageError, r"^Can't use -o with &file$"): 

826 self.cog.callable_main(["argv0", "-o", "foo", "&cogfiles.txt"]) 

827 

828 def test_no_diff_without_check(self): 

829 with self.assertRaisesRegex( 

830 CogUsageError, r"^Can't use --diff without --check$" 

831 ): 

832 self.cog.callable_main(["argv0", "--diff"]) 

833 

834 def test_dash_v(self): 

835 self.assertEqual(self.cog.main(["argv0", "-v"]), 0) 

836 output = self.output.getvalue() 

837 self.assertEqual("Cog version %s\n" % __version__, output) 

838 

839 def produces_help(self, args): 

840 self.new_cog() 

841 argv = ["argv0"] + args.split() 

842 self.assertEqual(self.cog.main(argv), 0) 

843 self.assertEqual(usage, self.output.getvalue()) 

844 

845 def test_dash_h(self): 

846 # -h, --help, or -? anywhere on the command line should just print help. 

847 self.produces_help("-h") 

848 self.produces_help("--help") 

849 self.produces_help("-?") 

850 self.produces_help("fooey.txt -h") 

851 self.produces_help("fooey.txt --help") 

852 self.produces_help("-o -r @fooey.txt -? @booey.txt") 

853 

854 def test_dash_o_and_dash_r(self): 

855 d = { 

856 "cogfile.txt": """\ 

857 # Please run cog 

858 """ 

859 } 

860 

861 make_files(d) 

862 with self.assertRaisesRegex( 

863 CogUsageError, r"^Can't use -o with -r \(they are opposites\)$" 

864 ): 

865 self.cog.callable_main(["argv0", "-o", "foo", "-r", "cogfile.txt"]) 

866 

867 def test_dash_z(self): 

868 d = { 

869 "test.cog": """\ 

870 // This is my C++ file. 

871 //[[[cog 

872 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

873 for fn in fnames: 

874 cog.outl("void %s();" % fn) 

875 //]]] 

876 """, 

877 "test.out": """\ 

878 // This is my C++ file. 

879 //[[[cog 

880 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

881 for fn in fnames: 

882 cog.outl("void %s();" % fn) 

883 //]]] 

884 void DoSomething(); 

885 void DoAnotherThing(); 

886 void DoLastThing(); 

887 """, 

888 } 

889 

890 make_files(d) 

891 with self.assertRaisesRegex( 

892 CogError, r"^test.cog\(6\): Missing '\[\[\[end\]\]\]' before end of file.$" 

893 ): 

894 self.cog.callable_main(["argv0", "-r", "test.cog"]) 

895 self.new_cog() 

896 self.cog.callable_main(["argv0", "-r", "-z", "test.cog"]) 

897 self.assertFilesSame("test.cog", "test.out") 

898 

899 def test_bad_dash_d(self): 

900 with self.assertRaisesRegex(CogUsageError, r"^-D takes a name=value argument$"): 

901 self.cog.callable_main(["argv0", "-Dfooey", "cog.txt"]) 

902 with self.assertRaisesRegex(CogUsageError, r"^-D takes a name=value argument$"): 

903 self.cog.callable_main(["argv0", "-D", "fooey", "cog.txt"]) 

904 

905 def test_bad_markers(self): 

906 with self.assertRaisesRegex( 

907 CogUsageError, 

908 r"^--markers requires 3 values separated by spaces, could not parse 'X'$", 

909 ): 

910 self.cog.callable_main(["argv0", "--markers=X"]) 

911 with self.assertRaisesRegex( 

912 CogUsageError, 

913 r"^--markers requires 3 values separated by spaces, could not parse 'A B C D'$", 

914 ): 

915 self.cog.callable_main(["argv0", "--markers=A B C D"]) 

916 

917 

918class TestMain(TestCaseWithTempDir): 

919 def setUp(self): 

920 super().setUp() 

921 self.old_argv = sys.argv[:] 

922 self.old_stderr = sys.stderr 

923 sys.stderr = io.StringIO() 

924 

925 def tearDown(self): 

926 sys.stderr = self.old_stderr 

927 sys.argv = self.old_argv 

928 sys.modules.pop("mycode", None) 

929 super().tearDown() 

930 

931 def test_main_function(self): 

932 sys.argv = ["argv0", "-Z"] 

933 ret = main() 

934 self.assertEqual(ret, 2) 

935 stderr = sys.stderr.getvalue() 

936 self.assertEqual(stderr, "option -Z not recognized\n(for help use --help)\n") 

937 

938 files = { 

939 "test.cog": """\ 

940 //[[[cog 

941 def func(): 

942 import mycode 

943 mycode.boom() 

944 //]]] 

945 //[[[end]]] 

946 ----- 

947 //[[[cog 

948 func() 

949 //]]] 

950 //[[[end]]] 

951 """, 

952 "mycode.py": """\ 

953 def boom(): 

954 [][0] 

955 """, 

956 } 

957 

958 def test_error_report(self): 

959 self.check_error_report() 

960 

961 def test_error_report_with_prologue(self): 

962 self.check_error_report("-p", "#1\n#2") 

963 

964 def check_error_report(self, *args): 

965 """Check that the error report is right.""" 

966 make_files(self.files) 

967 sys.argv = ["argv0"] + list(args) + ["-r", "test.cog"] 

968 main() 

969 expected = reindent_block("""\ 

970 Traceback (most recent call last): 

971 File "test.cog", line 9, in <module> 

972 func() 

973 File "test.cog", line 4, in func 

974 mycode.boom() 

975 File "MYCODE", line 2, in boom 

976 [][0] 

977 IndexError: list index out of range 

978 """) 

979 expected = expected.replace("MYCODE", os.path.abspath("mycode.py")) 

980 assert expected == sys.stderr.getvalue() 

981 

982 def test_error_in_prologue(self): 

983 make_files(self.files) 

984 sys.argv = ["argv0", "-p", "import mycode; mycode.boom()", "-r", "test.cog"] 

985 main() 

986 expected = reindent_block("""\ 

987 Traceback (most recent call last): 

988 File "<prologue>", line 1, in <module> 

989 import mycode; mycode.boom() 

990 File "MYCODE", line 2, in boom 

991 [][0] 

992 IndexError: list index out of range 

993 """) 

994 expected = expected.replace("MYCODE", os.path.abspath("mycode.py")) 

995 assert expected == sys.stderr.getvalue() 

996 

997 

998class TestFileHandling(TestCaseWithTempDir): 

999 def test_simple(self): 

1000 d = { 

1001 "test.cog": """\ 

1002 // This is my C++ file. 

1003 //[[[cog 

1004 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1005 for fn in fnames: 

1006 cog.outl("void %s();" % fn) 

1007 //]]] 

1008 //[[[end]]] 

1009 """, 

1010 "test.out": """\ 

1011 // This is my C++ file. 

1012 //[[[cog 

1013 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1014 for fn in fnames: 

1015 cog.outl("void %s();" % fn) 

1016 //]]] 

1017 void DoSomething(); 

1018 void DoAnotherThing(); 

1019 void DoLastThing(); 

1020 //[[[end]]] 

1021 """, 

1022 } 

1023 

1024 make_files(d) 

1025 self.cog.callable_main(["argv0", "-r", "test.cog"]) 

1026 self.assertFilesSame("test.cog", "test.out") 

1027 output = self.output.getvalue() 

1028 self.assertIn("(changed)", output) 

1029 

1030 def test_print_output(self): 

1031 d = { 

1032 "test.cog": """\ 

1033 // This is my C++ file. 

1034 //[[[cog 

1035 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1036 for fn in fnames: 

1037 print("void %s();" % fn) 

1038 //]]] 

1039 //[[[end]]] 

1040 """, 

1041 "test.out": """\ 

1042 // This is my C++ file. 

1043 //[[[cog 

1044 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1045 for fn in fnames: 

1046 print("void %s();" % fn) 

1047 //]]] 

1048 void DoSomething(); 

1049 void DoAnotherThing(); 

1050 void DoLastThing(); 

1051 //[[[end]]] 

1052 """, 

1053 } 

1054 

1055 make_files(d) 

1056 self.cog.callable_main(["argv0", "-rP", "test.cog"]) 

1057 self.assertFilesSame("test.cog", "test.out") 

1058 output = self.output.getvalue() 

1059 self.assertIn("(changed)", output) 

1060 

1061 def test_wildcards(self): 

1062 d = { 

1063 "test.cog": """\ 

1064 // This is my C++ file. 

1065 //[[[cog 

1066 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1067 for fn in fnames: 

1068 cog.outl("void %s();" % fn) 

1069 //]]] 

1070 //[[[end]]] 

1071 """, 

1072 "test2.cog": """\ 

1073 // This is my C++ file. 

1074 //[[[cog 

1075 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1076 for fn in fnames: 

1077 cog.outl("void %s();" % fn) 

1078 //]]] 

1079 //[[[end]]] 

1080 """, 

1081 "test.out": """\ 

1082 // This is my C++ file. 

1083 //[[[cog 

1084 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1085 for fn in fnames: 

1086 cog.outl("void %s();" % fn) 

1087 //]]] 

1088 void DoSomething(); 

1089 void DoAnotherThing(); 

1090 void DoLastThing(); 

1091 //[[[end]]] 

1092 """, 

1093 "not_this_one.cog": """\ 

1094 // This is my C++ file. 

1095 //[[[cog 

1096 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1097 for fn in fnames: 

1098 cog.outl("void %s();" % fn) 

1099 //]]] 

1100 //[[[end]]] 

1101 """, 

1102 "not_this_one.out": """\ 

1103 // This is my C++ file. 

1104 //[[[cog 

1105 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1106 for fn in fnames: 

1107 cog.outl("void %s();" % fn) 

1108 //]]] 

1109 //[[[end]]] 

1110 """, 

1111 } 

1112 

1113 make_files(d) 

1114 self.cog.callable_main(["argv0", "-r", "t*.cog"]) 

1115 self.assertFilesSame("test.cog", "test.out") 

1116 self.assertFilesSame("test2.cog", "test.out") 

1117 self.assertFilesSame("not_this_one.cog", "not_this_one.out") 

1118 output = self.output.getvalue() 

1119 self.assertIn("(changed)", output) 

1120 

1121 def test_output_file(self): 

1122 # -o sets the output file. 

1123 d = { 

1124 "test.cog": """\ 

1125 // This is my C++ file. 

1126 //[[[cog 

1127 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1128 for fn in fnames: 

1129 cog.outl("void %s();" % fn) 

1130 //]]] 

1131 //[[[end]]] 

1132 """, 

1133 "test.out": """\ 

1134 // This is my C++ file. 

1135 //[[[cog 

1136 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

1137 for fn in fnames: 

1138 cog.outl("void %s();" % fn) 

1139 //]]] 

1140 void DoSomething(); 

1141 void DoAnotherThing(); 

1142 void DoLastThing(); 

1143 //[[[end]]] 

1144 """, 

1145 } 

1146 

1147 make_files(d) 

1148 self.cog.callable_main(["argv0", "-o", "in/a/dir/test.cogged", "test.cog"]) 

1149 self.assertFilesSame("in/a/dir/test.cogged", "test.out") 

1150 

1151 def test_at_file(self): 

1152 d = { 

1153 "one.cog": """\ 

1154 //[[[cog 

1155 cog.outl("hello world") 

1156 //]]] 

1157 //[[[end]]] 

1158 """, 

1159 "one.out": """\ 

1160 //[[[cog 

1161 cog.outl("hello world") 

1162 //]]] 

1163 hello world 

1164 //[[[end]]] 

1165 """, 

1166 "two.cog": """\ 

1167 //[[[cog 

1168 cog.outl("goodbye cruel world") 

1169 //]]] 

1170 //[[[end]]] 

1171 """, 

1172 "two.out": """\ 

1173 //[[[cog 

1174 cog.outl("goodbye cruel world") 

1175 //]]] 

1176 goodbye cruel world 

1177 //[[[end]]] 

1178 """, 

1179 "cogfiles.txt": """\ 

1180 # Please run cog 

1181 one.cog 

1182 

1183 two.cog 

1184 """, 

1185 } 

1186 

1187 make_files(d) 

1188 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"]) 

1189 self.assertFilesSame("one.cog", "one.out") 

1190 self.assertFilesSame("two.cog", "two.out") 

1191 output = self.output.getvalue() 

1192 self.assertIn("(changed)", output) 

1193 

1194 def test_nested_at_file(self): 

1195 d = { 

1196 "one.cog": """\ 

1197 //[[[cog 

1198 cog.outl("hello world") 

1199 //]]] 

1200 //[[[end]]] 

1201 """, 

1202 "one.out": """\ 

1203 //[[[cog 

1204 cog.outl("hello world") 

1205 //]]] 

1206 hello world 

1207 //[[[end]]] 

1208 """, 

1209 "two.cog": """\ 

1210 //[[[cog 

1211 cog.outl("goodbye cruel world") 

1212 //]]] 

1213 //[[[end]]] 

1214 """, 

1215 "two.out": """\ 

1216 //[[[cog 

1217 cog.outl("goodbye cruel world") 

1218 //]]] 

1219 goodbye cruel world 

1220 //[[[end]]] 

1221 """, 

1222 "cogfiles.txt": """\ 

1223 # Please run cog 

1224 one.cog 

1225 @cogfiles2.txt 

1226 """, 

1227 "cogfiles2.txt": """\ 

1228 # This one too, please. 

1229 two.cog 

1230 """, 

1231 } 

1232 

1233 make_files(d) 

1234 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"]) 

1235 self.assertFilesSame("one.cog", "one.out") 

1236 self.assertFilesSame("two.cog", "two.out") 

1237 output = self.output.getvalue() 

1238 self.assertIn("(changed)", output) 

1239 

1240 def test_at_file_with_args(self): 

1241 d = { 

1242 "both.cog": """\ 

1243 //[[[cog 

1244 cog.outl("one: %s" % ('one' in globals())) 

1245 cog.outl("two: %s" % ('two' in globals())) 

1246 //]]] 

1247 //[[[end]]] 

1248 """, 

1249 "one.out": """\ 

1250 //[[[cog 

1251 cog.outl("one: %s" % ('one' in globals())) 

1252 cog.outl("two: %s" % ('two' in globals())) 

1253 //]]] 

1254 one: True // ONE 

1255 two: False // ONE 

1256 //[[[end]]] 

1257 """, 

1258 "two.out": """\ 

1259 //[[[cog 

1260 cog.outl("one: %s" % ('one' in globals())) 

1261 cog.outl("two: %s" % ('two' in globals())) 

1262 //]]] 

1263 one: False // TWO 

1264 two: True // TWO 

1265 //[[[end]]] 

1266 """, 

1267 "cogfiles.txt": """\ 

1268 # Please run cog 

1269 both.cog -o in/a/dir/both.one -s ' // ONE' -D one=x 

1270 both.cog -o in/a/dir/both.two -s ' // TWO' -D two=x 

1271 """, 

1272 } 

1273 

1274 make_files(d) 

1275 self.cog.callable_main(["argv0", "@cogfiles.txt"]) 

1276 self.assertFilesSame("in/a/dir/both.one", "one.out") 

1277 self.assertFilesSame("in/a/dir/both.two", "two.out") 

1278 

1279 def test_at_file_with_bad_arg_combo(self): 

1280 d = { 

1281 "both.cog": """\ 

1282 //[[[cog 

1283 cog.outl("one: %s" % ('one' in globals())) 

1284 cog.outl("two: %s" % ('two' in globals())) 

1285 //]]] 

1286 //[[[end]]] 

1287 """, 

1288 "cogfiles.txt": """\ 

1289 # Please run cog 

1290 both.cog 

1291 both.cog -d # This is bad: -r and -d 

1292 """, 

1293 } 

1294 

1295 make_files(d) 

1296 with self.assertRaisesRegex( 

1297 CogUsageError, 

1298 r"^Can't use -d with -r \(or you would delete all your source!\)$", 

1299 ): 

1300 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"]) 

1301 

1302 def test_at_file_with_tricky_filenames(self): 

1303 def fix_backslashes(files_txt): 

1304 """Make the contents of a files.txt sensitive to the platform.""" 

1305 if sys.platform != "win32": 

1306 files_txt = files_txt.replace("\\", "/") 

1307 return files_txt 

1308 

1309 d = { 

1310 "one 1.cog": """\ 

1311 //[[[cog cog.outl("hello world") ]]] 

1312 """, 

1313 "one.out": """\ 

1314 //[[[cog cog.outl("hello world") ]]] 

1315 hello world //xxx 

1316 """, 

1317 "subdir": { 

1318 "subback.cog": """\ 

1319 //[[[cog cog.outl("down deep with backslashes") ]]] 

1320 """, 

1321 "subfwd.cog": """\ 

1322 //[[[cog cog.outl("down deep with slashes") ]]] 

1323 """, 

1324 }, 

1325 "subback.out": """\ 

1326 //[[[cog cog.outl("down deep with backslashes") ]]] 

1327 down deep with backslashes //yyy 

1328 """, 

1329 "subfwd.out": """\ 

1330 //[[[cog cog.outl("down deep with slashes") ]]] 

1331 down deep with slashes //zzz 

1332 """, 

1333 "cogfiles.txt": fix_backslashes("""\ 

1334 # Please run cog 

1335 'one 1.cog' -s ' //xxx' 

1336 subdir\\subback.cog -s ' //yyy' 

1337 subdir/subfwd.cog -s ' //zzz' 

1338 """), 

1339 } 

1340 

1341 make_files(d) 

1342 self.cog.callable_main(["argv0", "-z", "-r", "@cogfiles.txt"]) 

1343 self.assertFilesSame("one 1.cog", "one.out") 

1344 self.assertFilesSame("subdir/subback.cog", "subback.out") 

1345 self.assertFilesSame("subdir/subfwd.cog", "subfwd.out") 

1346 

1347 def test_amp_file(self): 

1348 d = { 

1349 "code": { 

1350 "files_to_cog": """\ 

1351 # A locally resolved file name. 

1352 test.cog 

1353 """, 

1354 "test.cog": """\ 

1355 //[[[cog 

1356 import myampsubmodule 

1357 //]]] 

1358 //[[[end]]] 

1359 """, 

1360 "test.out": """\ 

1361 //[[[cog 

1362 import myampsubmodule 

1363 //]]] 

1364 Hello from myampsubmodule 

1365 //[[[end]]] 

1366 """, 

1367 "myampsubmodule.py": """\ 

1368 import cog 

1369 cog.outl("Hello from myampsubmodule") 

1370 """, 

1371 } 

1372 } 

1373 

1374 make_files(d) 

1375 print(os.path.abspath("code/test.out")) 

1376 self.cog.callable_main(["argv0", "-r", "&code/files_to_cog"]) 

1377 self.assertFilesSame("code/test.cog", "code/test.out") 

1378 

1379 def run_with_verbosity(self, verbosity): 

1380 d = { 

1381 "unchanged.cog": """\ 

1382 //[[[cog 

1383 cog.outl("hello world") 

1384 //]]] 

1385 hello world 

1386 //[[[end]]] 

1387 """, 

1388 "changed.cog": """\ 

1389 //[[[cog 

1390 cog.outl("goodbye cruel world") 

1391 //]]] 

1392 //[[[end]]] 

1393 """, 

1394 "cogfiles.txt": """\ 

1395 unchanged.cog 

1396 changed.cog 

1397 """, 

1398 } 

1399 

1400 make_files(d) 

1401 self.cog.callable_main( 

1402 ["argv0", "-r", "--verbosity=" + verbosity, "@cogfiles.txt"] 

1403 ) 

1404 output = self.output.getvalue() 

1405 return output 

1406 

1407 def test_verbosity0(self): 

1408 output = self.run_with_verbosity("0") 

1409 self.assertEqual(output, "") 

1410 

1411 def test_verbosity1(self): 

1412 output = self.run_with_verbosity("1") 

1413 self.assertEqual(output, "Cogging changed.cog (changed)\n") 

1414 

1415 def test_verbosity2(self): 

1416 output = self.run_with_verbosity("2") 

1417 self.assertEqual( 

1418 output, "Cogging unchanged.cog\nCogging changed.cog (changed)\n" 

1419 ) 

1420 

1421 def test_change_dir(self): 

1422 # The code can change directories, cog will move us back. 

1423 d = { 

1424 "sub": { 

1425 "data.txt": "Hello!", 

1426 }, 

1427 "test.cog": """\ 

1428 //[[[cog 

1429 import os 

1430 os.chdir("sub") 

1431 cog.outl(open("data.txt").read()) 

1432 //]]] 

1433 //[[[end]]] 

1434 """, 

1435 "test.out": """\ 

1436 //[[[cog 

1437 import os 

1438 os.chdir("sub") 

1439 cog.outl(open("data.txt").read()) 

1440 //]]] 

1441 Hello! 

1442 //[[[end]]] 

1443 """, 

1444 } 

1445 

1446 make_files(d) 

1447 self.cog.callable_main(["argv0", "-r", "test.cog"]) 

1448 self.assertFilesSame("test.cog", "test.out") 

1449 output = self.output.getvalue() 

1450 self.assertIn("(changed)", output) 

1451 

1452 

1453class CogTestLineEndings(TestCaseWithTempDir): 

1454 """Tests for -U option (force LF line-endings in output).""" 

1455 

1456 lines_in = [ 

1457 "Some text.", 

1458 "//[[[cog", 

1459 'cog.outl("Cog text")', 

1460 "//]]]", 

1461 "gobbledegook.", 

1462 "//[[[end]]]", 

1463 "epilogue.", 

1464 "", 

1465 ] 

1466 

1467 lines_out = [ 

1468 "Some text.", 

1469 "//[[[cog", 

1470 'cog.outl("Cog text")', 

1471 "//]]]", 

1472 "Cog text", 

1473 "//[[[end]]]", 

1474 "epilogue.", 

1475 "", 

1476 ] 

1477 

1478 def test_output_native_eol(self): 

1479 make_files({"infile": "\n".join(self.lines_in)}) 

1480 self.cog.callable_main(["argv0", "-o", "outfile", "infile"]) 

1481 self.assertFileContent("outfile", os.linesep.join(self.lines_out)) 

1482 

1483 def test_output_lf_eol(self): 

1484 make_files({"infile": "\n".join(self.lines_in)}) 

1485 self.cog.callable_main(["argv0", "-U", "-o", "outfile", "infile"]) 

1486 self.assertFileContent("outfile", "\n".join(self.lines_out)) 

1487 

1488 def test_replace_native_eol(self): 

1489 make_files({"test.cog": "\n".join(self.lines_in)}) 

1490 self.cog.callable_main(["argv0", "-r", "test.cog"]) 

1491 self.assertFileContent("test.cog", os.linesep.join(self.lines_out)) 

1492 

1493 def test_replace_lf_eol(self): 

1494 make_files({"test.cog": "\n".join(self.lines_in)}) 

1495 self.cog.callable_main(["argv0", "-U", "-r", "test.cog"]) 

1496 self.assertFileContent("test.cog", "\n".join(self.lines_out)) 

1497 

1498 

1499class CogTestCharacterEncoding(TestCaseWithTempDir): 

1500 def test_simple(self): 

1501 d = { 

1502 "test.cog": b"""\ 

1503 // This is my C++ file. 

1504 //[[[cog 

1505 cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)") 

1506 //]]] 

1507 //[[[end]]] 

1508 """, 

1509 "test.out": b"""\ 

1510 // This is my C++ file. 

1511 //[[[cog 

1512 cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)") 

1513 //]]] 

1514 // Unicode: \xe1\x88\xb4 (U+1234) 

1515 //[[[end]]] 

1516 """.replace(b"\n", os.linesep.encode()), 

1517 } 

1518 

1519 make_files(d) 

1520 self.cog.callable_main(["argv0", "-r", "test.cog"]) 

1521 self.assertFilesSame("test.cog", "test.out") 

1522 output = self.output.getvalue() 

1523 self.assertIn("(changed)", output) 

1524 

1525 def test_file_encoding_option(self): 

1526 d = { 

1527 "test.cog": b"""\ 

1528 // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows 

1529 //[[[cog 

1530 cog.outl("\xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe") 

1531 //]]] 

1532 //[[[end]]] 

1533 """, 

1534 "test.out": b"""\ 

1535 // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows 

1536 //[[[cog 

1537 cog.outl("\xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe") 

1538 //]]] 

1539 \xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe 

1540 //[[[end]]] 

1541 """.replace(b"\n", os.linesep.encode()), 

1542 } 

1543 

1544 make_files(d) 

1545 self.cog.callable_main(["argv0", "-n", "cp1251", "-r", "test.cog"]) 

1546 self.assertFilesSame("test.cog", "test.out") 

1547 output = self.output.getvalue() 

1548 self.assertIn("(changed)", output) 

1549 

1550 

1551class TestCaseWithImports(TestCaseWithTempDir): 

1552 """Automatic resetting of sys.modules for tests that import modules. 

1553 

1554 When running tests which import modules, the sys.modules list 

1555 leaks from one test to the next. This test case class scrubs 

1556 the list after each run to keep the tests isolated from each other. 

1557 

1558 """ 

1559 

1560 def setUp(self): 

1561 super().setUp() 

1562 self.sysmodulekeys = list(sys.modules) 

1563 

1564 def tearDown(self): 

1565 modstoscrub = [ 

1566 modname for modname in sys.modules if modname not in self.sysmodulekeys 

1567 ] 

1568 for modname in modstoscrub: 

1569 del sys.modules[modname] 

1570 super().tearDown() 

1571 

1572 

1573class CogIncludeTests(TestCaseWithImports): 

1574 dincludes = { 

1575 "test.cog": """\ 

1576 //[[[cog 

1577 import mymodule 

1578 //]]] 

1579 //[[[end]]] 

1580 """, 

1581 "test.out": """\ 

1582 //[[[cog 

1583 import mymodule 

1584 //]]] 

1585 Hello from mymodule 

1586 //[[[end]]] 

1587 """, 

1588 "test2.out": """\ 

1589 //[[[cog 

1590 import mymodule 

1591 //]]] 

1592 Hello from mymodule in inc2 

1593 //[[[end]]] 

1594 """, 

1595 "include": { 

1596 "mymodule.py": """\ 

1597 import cog 

1598 cog.outl("Hello from mymodule") 

1599 """ 

1600 }, 

1601 "inc2": { 

1602 "mymodule.py": """\ 

1603 import cog 

1604 cog.outl("Hello from mymodule in inc2") 

1605 """ 

1606 }, 

1607 "inc3": { 

1608 "someothermodule.py": """\ 

1609 import cog 

1610 cog.outl("This is some other module.") 

1611 """ 

1612 }, 

1613 } 

1614 

1615 def test_need_include_path(self): 

1616 # Try it without the -I, to see that an ImportError happens. 

1617 make_files(self.dincludes) 

1618 msg = "(ImportError|ModuleNotFoundError): No module named '?mymodule'?" 

1619 with self.assertRaisesRegex(CogUserException, msg): 

1620 self.cog.callable_main(["argv0", "-r", "test.cog"]) 

1621 

1622 def test_include_path(self): 

1623 # Test that -I adds include directories properly. 

1624 make_files(self.dincludes) 

1625 self.cog.callable_main(["argv0", "-r", "-I", "include", "test.cog"]) 

1626 self.assertFilesSame("test.cog", "test.out") 

1627 

1628 def test_two_include_paths(self): 

1629 # Test that two -I's add include directories properly. 

1630 make_files(self.dincludes) 

1631 self.cog.callable_main( 

1632 ["argv0", "-r", "-I", "include", "-I", "inc2", "test.cog"] 

1633 ) 

1634 self.assertFilesSame("test.cog", "test.out") 

1635 

1636 def test_two_include_paths2(self): 

1637 # Test that two -I's add include directories properly. 

1638 make_files(self.dincludes) 

1639 self.cog.callable_main( 

1640 ["argv0", "-r", "-I", "inc2", "-I", "include", "test.cog"] 

1641 ) 

1642 self.assertFilesSame("test.cog", "test2.out") 

1643 

1644 def test_useless_include_path(self): 

1645 # Test that the search will continue past the first directory. 

1646 make_files(self.dincludes) 

1647 self.cog.callable_main( 

1648 ["argv0", "-r", "-I", "inc3", "-I", "include", "test.cog"] 

1649 ) 

1650 self.assertFilesSame("test.cog", "test.out") 

1651 

1652 def test_sys_path_is_unchanged(self): 

1653 d = { 

1654 "bad.cog": """\ 

1655 //[[[cog cog.error("Oh no!") ]]] 

1656 //[[[end]]] 

1657 """, 

1658 "good.cog": """\ 

1659 //[[[cog cog.outl("Oh yes!") ]]] 

1660 //[[[end]]] 

1661 """, 

1662 } 

1663 

1664 make_files(d) 

1665 # Is it unchanged just by creating a cog engine? 

1666 oldsyspath = sys.path[:] 

1667 self.new_cog() 

1668 self.assertEqual(oldsyspath, sys.path) 

1669 # Is it unchanged for a successful run? 

1670 self.new_cog() 

1671 self.cog.callable_main(["argv0", "-r", "good.cog"]) 

1672 self.assertEqual(oldsyspath, sys.path) 

1673 # Is it unchanged for a successful run with includes? 

1674 self.new_cog() 

1675 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "good.cog"]) 

1676 self.assertEqual(oldsyspath, sys.path) 

1677 # Is it unchanged for a successful run with two includes? 

1678 self.new_cog() 

1679 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "-I", "quux", "good.cog"]) 

1680 self.assertEqual(oldsyspath, sys.path) 

1681 # Is it unchanged for a failed run? 

1682 self.new_cog() 

1683 with self.assertRaisesRegex(CogError, r"^Oh no!$"): 

1684 self.cog.callable_main(["argv0", "-r", "bad.cog"]) 

1685 self.assertEqual(oldsyspath, sys.path) 

1686 # Is it unchanged for a failed run with includes? 

1687 self.new_cog() 

1688 with self.assertRaisesRegex(CogError, r"^Oh no!$"): 

1689 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "bad.cog"]) 

1690 self.assertEqual(oldsyspath, sys.path) 

1691 # Is it unchanged for a failed run with two includes? 

1692 self.new_cog() 

1693 with self.assertRaisesRegex(CogError, r"^Oh no!$"): 

1694 self.cog.callable_main( 

1695 ["argv0", "-r", "-I", "xyzzy", "-I", "quux", "bad.cog"] 

1696 ) 

1697 self.assertEqual(oldsyspath, sys.path) 

1698 

1699 def test_sub_directories(self): 

1700 # Test that relative paths on the command line work, with includes. 

1701 

1702 d = { 

1703 "code": { 

1704 "test.cog": """\ 

1705 //[[[cog 

1706 import mysubmodule 

1707 //]]] 

1708 //[[[end]]] 

1709 """, 

1710 "test.out": """\ 

1711 //[[[cog 

1712 import mysubmodule 

1713 //]]] 

1714 Hello from mysubmodule 

1715 //[[[end]]] 

1716 """, 

1717 "mysubmodule.py": """\ 

1718 import cog 

1719 cog.outl("Hello from mysubmodule") 

1720 """, 

1721 } 

1722 } 

1723 

1724 make_files(d) 

1725 # We should be able to invoke cog without the -I switch, and it will 

1726 # auto-include the current directory 

1727 self.cog.callable_main(["argv0", "-r", "code/test.cog"]) 

1728 self.assertFilesSame("code/test.cog", "code/test.out") 

1729 

1730 

1731class CogTestsInFiles(TestCaseWithTempDir): 

1732 def test_warn_if_no_cog_code(self): 

1733 # Test that the -e switch warns if there is no Cog code. 

1734 d = { 

1735 "with.cog": """\ 

1736 //[[[cog 

1737 cog.outl("hello world") 

1738 //]]] 

1739 hello world 

1740 //[[[end]]] 

1741 """, 

1742 "without.cog": """\ 

1743 There's no cog 

1744 code in this file. 

1745 """, 

1746 } 

1747 

1748 make_files(d) 

1749 self.cog.callable_main(["argv0", "-e", "with.cog"]) 

1750 output = self.output.getvalue() 

1751 self.assertNotIn("Warning", output) 

1752 self.new_cog() 

1753 self.cog.callable_main(["argv0", "-e", "without.cog"]) 

1754 output = self.output.getvalue() 

1755 self.assertIn("Warning: no cog code found in without.cog", output) 

1756 self.new_cog() 

1757 self.cog.callable_main(["argv0", "without.cog"]) 

1758 output = self.output.getvalue() 

1759 self.assertNotIn("Warning", output) 

1760 

1761 def test_file_name_props(self): 

1762 d = { 

1763 "cog1.txt": """\ 

1764 //[[[cog 

1765 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile)) 

1766 //]]] 

1767 this is cog1.txt in, cog1.txt out 

1768 [[[end]]] 

1769 """, 

1770 "cog1.out": """\ 

1771 //[[[cog 

1772 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile)) 

1773 //]]] 

1774 This is cog1.txt in, cog1.txt out 

1775 [[[end]]] 

1776 """, 

1777 "cog1out.out": """\ 

1778 //[[[cog 

1779 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile)) 

1780 //]]] 

1781 This is cog1.txt in, cog1out.txt out 

1782 [[[end]]] 

1783 """, 

1784 } 

1785 

1786 make_files(d) 

1787 self.cog.callable_main(["argv0", "-r", "cog1.txt"]) 

1788 self.assertFilesSame("cog1.txt", "cog1.out") 

1789 self.new_cog() 

1790 self.cog.callable_main(["argv0", "-o", "cog1out.txt", "cog1.txt"]) 

1791 self.assertFilesSame("cog1out.txt", "cog1out.out") 

1792 

1793 def test_globals_dont_cross_files(self): 

1794 # Make sure that global values don't get shared between files. 

1795 d = { 

1796 "one.cog": """\ 

1797 //[[[cog s = "This was set in one.cog" ]]] 

1798 //[[[end]]] 

1799 //[[[cog cog.outl(s) ]]] 

1800 //[[[end]]] 

1801 """, 

1802 "one.out": """\ 

1803 //[[[cog s = "This was set in one.cog" ]]] 

1804 //[[[end]]] 

1805 //[[[cog cog.outl(s) ]]] 

1806 This was set in one.cog 

1807 //[[[end]]] 

1808 """, 

1809 "two.cog": """\ 

1810 //[[[cog 

1811 try: 

1812 cog.outl(s) 

1813 except NameError: 

1814 cog.outl("s isn't set!") 

1815 //]]] 

1816 //[[[end]]] 

1817 """, 

1818 "two.out": """\ 

1819 //[[[cog 

1820 try: 

1821 cog.outl(s) 

1822 except NameError: 

1823 cog.outl("s isn't set!") 

1824 //]]] 

1825 s isn't set! 

1826 //[[[end]]] 

1827 """, 

1828 "cogfiles.txt": """\ 

1829 # Please run cog 

1830 one.cog 

1831 

1832 two.cog 

1833 """, 

1834 } 

1835 

1836 make_files(d) 

1837 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"]) 

1838 self.assertFilesSame("one.cog", "one.out") 

1839 self.assertFilesSame("two.cog", "two.out") 

1840 output = self.output.getvalue() 

1841 self.assertIn("(changed)", output) 

1842 

1843 def test_remove_generated_output(self): 

1844 d = { 

1845 "cog1.txt": """\ 

1846 //[[[cog 

1847 cog.outl("This line was generated.") 

1848 //]]] 

1849 This line was generated. 

1850 //[[[end]]] 

1851 This line was not. 

1852 """, 

1853 "cog1.out": """\ 

1854 //[[[cog 

1855 cog.outl("This line was generated.") 

1856 //]]] 

1857 //[[[end]]] 

1858 This line was not. 

1859 """, 

1860 "cog1.out2": """\ 

1861 //[[[cog 

1862 cog.outl("This line was generated.") 

1863 //]]] 

1864 This line was generated. 

1865 //[[[end]]] 

1866 This line was not. 

1867 """, 

1868 } 

1869 

1870 make_files(d) 

1871 # Remove generated output. 

1872 self.cog.callable_main(["argv0", "-r", "-x", "cog1.txt"]) 

1873 self.assertFilesSame("cog1.txt", "cog1.out") 

1874 self.new_cog() 

1875 # Regenerate the generated output. 

1876 self.cog.callable_main(["argv0", "-r", "cog1.txt"]) 

1877 self.assertFilesSame("cog1.txt", "cog1.out2") 

1878 self.new_cog() 

1879 # Remove the generated output again. 

1880 self.cog.callable_main(["argv0", "-r", "-x", "cog1.txt"]) 

1881 self.assertFilesSame("cog1.txt", "cog1.out") 

1882 

1883 def test_msg_call(self): 

1884 infile = """\ 

1885 #[[[cog 

1886 cog.msg("Hello there!") 

1887 #]]] 

1888 #[[[end]]] 

1889 """ 

1890 infile = reindent_block(infile) 

1891 self.assertEqual(self.cog.process_string(infile), infile) 

1892 output = self.output.getvalue() 

1893 self.assertEqual(output, "Message: Hello there!\n") 

1894 

1895 def test_error_message_has_no_traceback(self): 

1896 # Test that a Cog error is printed to stderr with no traceback. 

1897 

1898 d = { 

1899 "cog1.txt": """\ 

1900 //[[[cog 

1901 cog.outl("This line was newly") 

1902 cog.outl("generated by cog") 

1903 cog.outl("blah blah.") 

1904 //]]] 

1905 Xhis line was newly 

1906 generated by cog 

1907 blah blah. 

1908 //[[[end]]] (sum: qFQJguWta5) 

1909 """, 

1910 } 

1911 

1912 make_files(d) 

1913 stderr = io.StringIO() 

1914 self.cog.set_output(stderr=stderr) 

1915 self.cog.main(["argv0", "-c", "-r", "cog1.txt"]) 

1916 self.assertEqual(self.output.getvalue(), "Cogging cog1.txt\n") 

1917 self.assertEqual( 

1918 stderr.getvalue(), 

1919 "cog1.txt(9): Output has been edited! Delete old checksum to unprotect.\n", 

1920 ) 

1921 

1922 def test_dash_d(self): 

1923 d = { 

1924 "test.cog": """\ 

1925 --[[[cog cog.outl("Defined fooey as " + fooey) ]]] 

1926 --[[[end]]] 

1927 """, 

1928 "test.kablooey": """\ 

1929 --[[[cog cog.outl("Defined fooey as " + fooey) ]]] 

1930 Defined fooey as kablooey 

1931 --[[[end]]] 

1932 """, 

1933 "test.einstein": """\ 

1934 --[[[cog cog.outl("Defined fooey as " + fooey) ]]] 

1935 Defined fooey as e=mc2 

1936 --[[[end]]] 

1937 """, 

1938 } 

1939 

1940 make_files(d) 

1941 self.cog.callable_main(["argv0", "-r", "-D", "fooey=kablooey", "test.cog"]) 

1942 self.assertFilesSame("test.cog", "test.kablooey") 

1943 make_files(d) 

1944 self.cog.callable_main(["argv0", "-r", "-Dfooey=kablooey", "test.cog"]) 

1945 self.assertFilesSame("test.cog", "test.kablooey") 

1946 make_files(d) 

1947 self.cog.callable_main(["argv0", "-r", "-Dfooey=e=mc2", "test.cog"]) 

1948 self.assertFilesSame("test.cog", "test.einstein") 

1949 make_files(d) 

1950 self.cog.callable_main( 

1951 ["argv0", "-r", "-Dbar=quux", "-Dfooey=kablooey", "test.cog"] 

1952 ) 

1953 self.assertFilesSame("test.cog", "test.kablooey") 

1954 make_files(d) 

1955 self.cog.callable_main( 

1956 ["argv0", "-r", "-Dfooey=kablooey", "-Dbar=quux", "test.cog"] 

1957 ) 

1958 self.assertFilesSame("test.cog", "test.kablooey") 

1959 make_files(d) 

1960 self.cog.callable_main( 

1961 ["argv0", "-r", "-Dfooey=gooey", "-Dfooey=kablooey", "test.cog"] 

1962 ) 

1963 self.assertFilesSame("test.cog", "test.kablooey") 

1964 

1965 def test_output_to_stdout(self): 

1966 d = { 

1967 "test.cog": """\ 

1968 --[[[cog cog.outl('Hey there!') ]]] 

1969 --[[[end]]] 

1970 """ 

1971 } 

1972 

1973 make_files(d) 

1974 stderr = io.StringIO() 

1975 self.cog.set_output(stderr=stderr) 

1976 self.cog.callable_main(["argv0", "test.cog"]) 

1977 output = self.output.getvalue() 

1978 outerr = stderr.getvalue() 

1979 self.assertEqual( 

1980 output, "--[[[cog cog.outl('Hey there!') ]]]\nHey there!\n--[[[end]]]\n" 

1981 ) 

1982 self.assertEqual(outerr, "") 

1983 

1984 def test_read_from_stdin(self): 

1985 stdin = io.StringIO("--[[[cog cog.outl('Wow') ]]]\n--[[[end]]]\n") 

1986 

1987 def restore_stdin(old_stdin): 

1988 sys.stdin = old_stdin 

1989 

1990 self.addCleanup(restore_stdin, sys.stdin) 

1991 sys.stdin = stdin 

1992 

1993 stderr = io.StringIO() 

1994 self.cog.set_output(stderr=stderr) 

1995 self.cog.callable_main(["argv0", "-"]) 

1996 output = self.output.getvalue() 

1997 outerr = stderr.getvalue() 

1998 self.assertEqual(output, "--[[[cog cog.outl('Wow') ]]]\nWow\n--[[[end]]]\n") 

1999 self.assertEqual(outerr, "") 

2000 

2001 def test_suffix_output_lines(self): 

2002 d = { 

2003 "test.cog": """\ 

2004 Hey there. 

2005 ;[[[cog cog.outl('a\\nb\\n \\nc') ]]] 

2006 ;[[[end]]] 

2007 Good bye. 

2008 """, 

2009 "test.out": """\ 

2010 Hey there. 

2011 ;[[[cog cog.outl('a\\nb\\n \\nc') ]]] 

2012 a (foo) 

2013 b (foo) 

2014 """ # These three trailing spaces are important. 

2015 # The suffix is not applied to completely blank lines. 

2016 """ 

2017 c (foo) 

2018 ;[[[end]]] 

2019 Good bye. 

2020 """, 

2021 } 

2022 

2023 make_files(d) 

2024 self.cog.callable_main(["argv0", "-r", "-s", " (foo)", "test.cog"]) 

2025 self.assertFilesSame("test.cog", "test.out") 

2026 

2027 def test_empty_suffix(self): 

2028 d = { 

2029 "test.cog": """\ 

2030 ;[[[cog cog.outl('a\\nb\\nc') ]]] 

2031 ;[[[end]]] 

2032 """, 

2033 "test.out": """\ 

2034 ;[[[cog cog.outl('a\\nb\\nc') ]]] 

2035 a 

2036 b 

2037 c 

2038 ;[[[end]]] 

2039 """, 

2040 } 

2041 

2042 make_files(d) 

2043 self.cog.callable_main(["argv0", "-r", "-s", "", "test.cog"]) 

2044 self.assertFilesSame("test.cog", "test.out") 

2045 

2046 def test_hellish_suffix(self): 

2047 d = { 

2048 "test.cog": """\ 

2049 ;[[[cog cog.outl('a\\n\\nb') ]]] 

2050 """, 

2051 "test.out": """\ 

2052 ;[[[cog cog.outl('a\\n\\nb') ]]] 

2053 a /\\n*+([)]>< 

2054 

2055 b /\\n*+([)]>< 

2056 """, 

2057 } 

2058 

2059 make_files(d) 

2060 self.cog.callable_main(["argv0", "-z", "-r", "-s", r" /\n*+([)]><", "test.cog"]) 

2061 self.assertFilesSame("test.cog", "test.out") 

2062 

2063 def test_prologue(self): 

2064 d = { 

2065 "test.cog": """\ 

2066 Some text. 

2067 //[[[cog cog.outl(str(math.sqrt(2))[:12])]]] 

2068 //[[[end]]] 

2069 epilogue. 

2070 """, 

2071 "test.out": """\ 

2072 Some text. 

2073 //[[[cog cog.outl(str(math.sqrt(2))[:12])]]] 

2074 1.4142135623 

2075 //[[[end]]] 

2076 epilogue. 

2077 """, 

2078 } 

2079 

2080 make_files(d) 

2081 self.cog.callable_main(["argv0", "-r", "-p", "import math", "test.cog"]) 

2082 self.assertFilesSame("test.cog", "test.out") 

2083 

2084 def test_threads(self): 

2085 # Test that the implicitly imported cog module is actually different for 

2086 # different threads. 

2087 numthreads = 20 

2088 

2089 d = {} 

2090 for i in range(numthreads): 

2091 d[f"f{i}.cog"] = ( 

2092 "x\n" * i 

2093 + "[[[cog\n" 

2094 + f"assert cog.firstLineNum == int(FIRST) == {i + 1}\n" 

2095 + "]]]\n" 

2096 + "[[[end]]]\n" 

2097 ) 

2098 make_files(d) 

2099 

2100 results = [] 

2101 

2102 def thread_main(num): 

2103 try: 

2104 ret = Cog().main( 

2105 ["cog.py", "-r", "-D", f"FIRST={num + 1}", f"f{num}.cog"] 

2106 ) 

2107 assert ret == 0 

2108 except Exception as exc: # pragma: no cover (only happens on test failure) 

2109 results.append(exc) 

2110 else: 

2111 results.append(None) 

2112 

2113 ts = [ 

2114 threading.Thread(target=thread_main, args=(i,)) for i in range(numthreads) 

2115 ] 

2116 for t in ts: 

2117 t.start() 

2118 for t in ts: 

2119 t.join() 

2120 assert results == [None] * numthreads 

2121 

2122 

2123class CheckTests(TestCaseWithTempDir): 

2124 def run_check(self, args, status=0): 

2125 actual_status = self.cog.main(["argv0", "--check"] + args) 

2126 print(self.output.getvalue()) 

2127 self.assertEqual(status, actual_status) 

2128 

2129 def assert_made_files_unchanged(self, d): 

2130 for name, content in d.items(): 

2131 content = reindent_block(content) 

2132 if os.name == "nt": 

2133 content = content.replace("\n", "\r\n") 

2134 self.assertFileContent(name, content) 

2135 

2136 def test_check_no_cog(self): 

2137 d = { 

2138 "hello.txt": """\ 

2139 Hello. 

2140 """, 

2141 } 

2142 make_files(d) 

2143 self.run_check(["hello.txt"], status=0) 

2144 self.assertEqual(self.output.getvalue(), "Checking hello.txt\n") 

2145 self.assert_made_files_unchanged(d) 

2146 

2147 def test_check_good(self): 

2148 d = { 

2149 "unchanged.cog": """\ 

2150 //[[[cog 

2151 cog.outl("hello world") 

2152 //]]] 

2153 hello world 

2154 //[[[end]]] 

2155 """, 

2156 } 

2157 make_files(d) 

2158 self.run_check(["unchanged.cog"], status=0) 

2159 self.assertEqual(self.output.getvalue(), "Checking unchanged.cog\n") 

2160 self.assert_made_files_unchanged(d) 

2161 

2162 def test_check_bad(self): 

2163 d = { 

2164 "changed.cog": """\ 

2165 //[[[cog 

2166 cog.outl("goodbye world") 

2167 //]]] 

2168 hello world 

2169 //[[[end]]] 

2170 """, 

2171 } 

2172 make_files(d) 

2173 self.run_check(["changed.cog"], status=5) 

2174 self.assertEqual( 

2175 self.output.getvalue(), "Checking changed.cog (changed)\nCheck failed\n" 

2176 ) 

2177 self.assert_made_files_unchanged(d) 

2178 

2179 def test_check_bad_with_diff(self): 

2180 d = { 

2181 "skittering.cog": """\ 

2182 //[[[cog 

2183 for i in range(5): cog.outl(f"number {i}") 

2184 cog.outl("goodbye world") 

2185 //]]] 

2186 number 0 

2187 number 1 

2188 number 2 

2189 number 3 

2190 number 4 

2191 hello world 

2192 //[[[end]]] 

2193 """, 

2194 } 

2195 make_files(d) 

2196 self.run_check(["--diff", "skittering.cog"], status=5) 

2197 output = """\ 

2198 Checking skittering.cog (changed) 

2199 --- current skittering.cog 

2200 +++ changed skittering.cog 

2201 @@ -7,5 +7,5 @@ 

2202 number 2 

2203 number 3 

2204 number 4 

2205 -hello world 

2206 +goodbye world 

2207 //[[[end]]] 

2208 Check failed 

2209 """ 

2210 self.assertEqual(self.output.getvalue(), reindent_block(output)) 

2211 self.assert_made_files_unchanged(d) 

2212 

2213 def test_check_mixed(self): 

2214 d = { 

2215 "unchanged.cog": """\ 

2216 //[[[cog 

2217 cog.outl("hello world") 

2218 //]]] 

2219 hello world 

2220 //[[[end]]] 

2221 """, 

2222 "changed.cog": """\ 

2223 //[[[cog 

2224 cog.outl("goodbye world") 

2225 //]]] 

2226 hello world 

2227 //[[[end]]] 

2228 """, 

2229 } 

2230 make_files(d) 

2231 for verbosity, output in [ 

2232 ("0", "Check failed\n"), 

2233 ("1", "Checking changed.cog (changed)\nCheck failed\n"), 

2234 ( 

2235 "2", 

2236 "Checking unchanged.cog\nChecking changed.cog (changed)\nCheck failed\n", 

2237 ), 

2238 ]: 

2239 self.new_cog() 

2240 self.run_check( 

2241 ["--verbosity=%s" % verbosity, "unchanged.cog", "changed.cog"], status=5 

2242 ) 

2243 self.assertEqual(self.output.getvalue(), output) 

2244 self.assert_made_files_unchanged(d) 

2245 

2246 def test_check_with_good_checksum(self): 

2247 d = { 

2248 "good.txt": """\ 

2249 //[[[cog 

2250 cog.outl("This line was newly") 

2251 cog.outl("generated by cog") 

2252 cog.outl("blah blah.") 

2253 //]]] 

2254 This line was newly 

2255 generated by cog 

2256 blah blah. 

2257 //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346) 

2258 """, 

2259 } 

2260 make_files(d) 

2261 # Have to use -c with --check if there are checksums in the file. 

2262 self.run_check(["-c", "good.txt"], status=0) 

2263 self.assertEqual(self.output.getvalue(), "Checking good.txt\n") 

2264 self.assert_made_files_unchanged(d) 

2265 

2266 def test_check_with_bad_checksum(self): 

2267 d = { 

2268 "bad.txt": """\ 

2269 //[[[cog 

2270 cog.outl("This line was newly") 

2271 cog.outl("generated by cog") 

2272 cog.outl("blah blah.") 

2273 //]]] 

2274 This line was newly 

2275 generated by cog 

2276 blah blah. 

2277 //[[[end]]] (checksum: a9999999e5ad6b95c9e9a184b26f4346) 

2278 """, 

2279 } 

2280 make_files(d) 

2281 # Have to use -c with --check if there are checksums in the file. 

2282 self.run_check(["-c", "bad.txt"], status=1) 

2283 self.assertEqual( 

2284 self.output.getvalue(), 

2285 "Checking bad.txt\nbad.txt(9): Output has been edited! Delete old checksum to unprotect.\n", 

2286 ) 

2287 self.assert_made_files_unchanged(d) 

2288 

2289 def test_check_with_good_sum(self): 

2290 d = { 

2291 "good.txt": """\ 

2292 //[[[cog 

2293 cog.outl("This line was newly") 

2294 cog.outl("generated by cog") 

2295 cog.outl("blah blah.") 

2296 //]]] 

2297 This line was newly 

2298 generated by cog 

2299 blah blah. 

2300 //[[[end]]] (sum: qFQJguWta5) 

2301 """, 

2302 } 

2303 make_files(d) 

2304 # Have to use -c with --check if there are checksums in the file. 

2305 self.run_check(["-c", "good.txt"], status=0) 

2306 self.assertEqual(self.output.getvalue(), "Checking good.txt\n") 

2307 self.assert_made_files_unchanged(d) 

2308 

2309 def test_check_with_bad_sum(self): 

2310 d = { 

2311 "bad.txt": """\ 

2312 //[[[cog 

2313 cog.outl("This line was newly") 

2314 cog.outl("generated by cog") 

2315 cog.outl("blah blah.") 

2316 //]]] 

2317 This line was newly 

2318 generated by cog 

2319 blah blah. 

2320 //[[[end]]] (sum: qZmZmeWta5) 

2321 """, 

2322 } 

2323 make_files(d) 

2324 # Have to use -c with --check if there are checksums in the file. 

2325 self.run_check(["-c", "bad.txt"], status=1) 

2326 self.assertEqual( 

2327 self.output.getvalue(), 

2328 "Checking bad.txt\nbad.txt(9): Output has been edited! Delete old checksum to unprotect.\n", 

2329 ) 

2330 self.assert_made_files_unchanged(d) 

2331 

2332 

2333class WritabilityTests(TestCaseWithTempDir): 

2334 d = { 

2335 "test.cog": """\ 

2336 //[[[cog 

2337 for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']: 

2338 cog.outl("void %s();" % fn) 

2339 //]]] 

2340 //[[[end]]] 

2341 """, 

2342 "test.out": """\ 

2343 //[[[cog 

2344 for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']: 

2345 cog.outl("void %s();" % fn) 

2346 //]]] 

2347 void DoSomething(); 

2348 void DoAnotherThing(); 

2349 void DoLastThing(); 

2350 //[[[end]]] 

2351 """, 

2352 } 

2353 

2354 if os.name == "nt": 2354 ↛ 2356line 2354 didn't jump to line 2356 because the condition on line 2354 was never true

2355 # for Windows 

2356 cmd_w_args = "attrib -R %s" 

2357 cmd_w_asterisk = "attrib -R *" 

2358 else: 

2359 # for unix-like 

2360 cmd_w_args = "chmod +w %s" 

2361 cmd_w_asterisk = "chmod +w *" 

2362 

2363 def setUp(self): 

2364 super().setUp() 

2365 make_files(self.d) 

2366 self.testcog = os.path.join(self.tempdir, "test.cog") 

2367 os.chmod(self.testcog, stat.S_IREAD) # Make the file readonly. 

2368 assert not os.access(self.testcog, os.W_OK) 

2369 

2370 def tearDown(self): 

2371 os.chmod(self.testcog, stat.S_IWRITE) # Make the file writable again. 

2372 super().tearDown() 

2373 

2374 def test_readonly_no_command(self): 

2375 with self.assertRaisesRegex(CogError, "^Can't overwrite test.cog$"): 

2376 self.cog.callable_main(["argv0", "-r", "test.cog"]) 

2377 assert not os.access(self.testcog, os.W_OK) 

2378 

2379 def test_readonly_with_command(self): 

2380 self.cog.callable_main(["argv0", "-r", "-w", self.cmd_w_args, "test.cog"]) 

2381 self.assertFilesSame("test.cog", "test.out") 

2382 assert os.access(self.testcog, os.W_OK) 

2383 

2384 def test_readonly_with_command_with_no_slot(self): 

2385 self.cog.callable_main(["argv0", "-r", "-w", self.cmd_w_asterisk, "test.cog"]) 

2386 self.assertFilesSame("test.cog", "test.out") 

2387 assert os.access(self.testcog, os.W_OK) 

2388 

2389 def test_readonly_with_ineffectual_command(self): 

2390 with self.assertRaisesRegex(CogError, "^Couldn't make test.cog writable$"): 

2391 self.cog.callable_main(["argv0", "-r", "-w", "echo %s", "test.cog"]) 

2392 assert not os.access(self.testcog, os.W_OK) 

2393 

2394 

2395class ChecksumTests(TestCaseWithTempDir): 

2396 def test_create_checksum_output(self): 

2397 d = { 

2398 "cog1.txt": """\ 

2399 //[[[cog 

2400 cog.outl("This line was generated.") 

2401 //]]] 

2402 This line was generated. 

2403 //[[[end]]] what 

2404 This line was not. 

2405 """, 

2406 "cog1.out": """\ 

2407 //[[[cog 

2408 cog.outl("This line was generated.") 

2409 //]]] 

2410 This line was generated. 

2411 //[[[end]]] (sum: itsT+1m5lq) what 

2412 This line was not. 

2413 """, 

2414 } 

2415 

2416 make_files(d) 

2417 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"]) 

2418 self.assertFilesSame("cog1.txt", "cog1.out") 

2419 

2420 def test_check_checksum_output(self): 

2421 d = { 

2422 "cog1.txt": """\ 

2423 //[[[cog 

2424 cog.outl("This line was newly") 

2425 cog.outl("generated by cog") 

2426 cog.outl("blah blah.") 

2427 //]]] 

2428 This line was generated. 

2429 //[[[end]]] (sum: itsT+1m5lq) end 

2430 """, 

2431 "cog1.out": """\ 

2432 //[[[cog 

2433 cog.outl("This line was newly") 

2434 cog.outl("generated by cog") 

2435 cog.outl("blah blah.") 

2436 //]]] 

2437 This line was newly 

2438 generated by cog 

2439 blah blah. 

2440 //[[[end]]] (sum: qFQJguWta5) end 

2441 """, 

2442 } 

2443 

2444 make_files(d) 

2445 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"]) 

2446 self.assertFilesSame("cog1.txt", "cog1.out") 

2447 

2448 def test_check_old_checksum_format(self): 

2449 # Test that old checksum format can still be read 

2450 d = { 

2451 "cog1.txt": """\ 

2452 //[[[cog 

2453 cog.outl("This line was newly") 

2454 cog.outl("generated by cog") 

2455 cog.outl("blah blah.") 

2456 //]]] 

2457 This line was generated. 

2458 //[[[end]]] (checksum: 8adb13fb59b996a1c7f0065ea9f3d893) end 

2459 """, 

2460 "cog1.out": """\ 

2461 //[[[cog 

2462 cog.outl("This line was newly") 

2463 cog.outl("generated by cog") 

2464 cog.outl("blah blah.") 

2465 //]]] 

2466 This line was newly 

2467 generated by cog 

2468 blah blah. 

2469 //[[[end]]] (sum: qFQJguWta5) end 

2470 """, 

2471 } 

2472 

2473 make_files(d) 

2474 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"]) 

2475 self.assertFilesSame("cog1.txt", "cog1.out") 

2476 

2477 def test_remove_checksum_output(self): 

2478 d = { 

2479 "cog1.txt": """\ 

2480 //[[[cog 

2481 cog.outl("This line was newly") 

2482 cog.outl("generated by cog") 

2483 cog.outl("blah blah.") 

2484 //]]] 

2485 This line was generated. 

2486 //[[[end]]] (sum: itsT+1m5lq) fooey 

2487 """, 

2488 "cog1.out": """\ 

2489 //[[[cog 

2490 cog.outl("This line was newly") 

2491 cog.outl("generated by cog") 

2492 cog.outl("blah blah.") 

2493 //]]] 

2494 This line was newly 

2495 generated by cog 

2496 blah blah. 

2497 //[[[end]]] fooey 

2498 """, 

2499 } 

2500 

2501 make_files(d) 

2502 self.cog.callable_main(["argv0", "-r", "cog1.txt"]) 

2503 self.assertFilesSame("cog1.txt", "cog1.out") 

2504 

2505 def test_tampered_checksum_output(self): 

2506 d = { 

2507 "cog1.txt": """\ 

2508 //[[[cog 

2509 cog.outl("This line was newly") 

2510 cog.outl("generated by cog") 

2511 cog.outl("blah blah.") 

2512 //]]] 

2513 Xhis line was newly 

2514 generated by cog 

2515 blah blah. 

2516 //[[[end]]] (sum: qFQJguWta5) 

2517 """, 

2518 "cog2.txt": """\ 

2519 //[[[cog 

2520 cog.outl("This line was newly") 

2521 cog.outl("generated by cog") 

2522 cog.outl("blah blah.") 

2523 //]]] 

2524 This line was newly 

2525 generated by cog 

2526 blah blah! 

2527 //[[[end]]] (sum: qFQJguWta5) 

2528 """, 

2529 "cog3.txt": """\ 

2530 //[[[cog 

2531 cog.outl("This line was newly") 

2532 cog.outl("generated by cog") 

2533 cog.outl("blah blah.") 

2534 //]]] 

2535 

2536 This line was newly 

2537 generated by cog 

2538 blah blah. 

2539 //[[[end]]] (sum: qFQJguWta5) 

2540 """, 

2541 "cog4.txt": """\ 

2542 //[[[cog 

2543 cog.outl("This line was newly") 

2544 cog.outl("generated by cog") 

2545 cog.outl("blah blah.") 

2546 //]]] 

2547 This line was newly 

2548 generated by cog 

2549 blah blah.. 

2550 //[[[end]]] (sum: qFQJguWta5) 

2551 """, 

2552 "cog5.txt": """\ 

2553 //[[[cog 

2554 cog.outl("This line was newly") 

2555 cog.outl("generated by cog") 

2556 cog.outl("blah blah.") 

2557 //]]] 

2558 This line was newly 

2559 generated by cog 

2560 blah blah. 

2561 extra 

2562 //[[[end]]] (sum: qFQJguWta5) 

2563 """, 

2564 "cog6.txt": """\ 

2565 //[[[cog 

2566 cog.outl("This line was newly") 

2567 cog.outl("generated by cog") 

2568 cog.outl("blah blah.") 

2569 //]]] 

2570 //[[[end]]] (sum: qFQJguWta5) 

2571 """, 

2572 } 

2573 

2574 make_files(d) 

2575 with self.assertRaisesRegex( 

2576 CogError, 

2577 r"^cog1.txt\(9\): Output has been edited! Delete old checksum to unprotect.$", 

2578 ): 

2579 self.cog.callable_main(["argv0", "-c", "cog1.txt"]) 

2580 with self.assertRaisesRegex( 

2581 CogError, 

2582 r"^cog2.txt\(9\): Output has been edited! Delete old checksum to unprotect.$", 

2583 ): 

2584 self.cog.callable_main(["argv0", "-c", "cog2.txt"]) 

2585 with self.assertRaisesRegex( 

2586 CogError, 

2587 r"^cog3.txt\(10\): Output has been edited! Delete old checksum to unprotect.$", 

2588 ): 

2589 self.cog.callable_main(["argv0", "-c", "cog3.txt"]) 

2590 with self.assertRaisesRegex( 

2591 CogError, 

2592 r"^cog4.txt\(9\): Output has been edited! Delete old checksum to unprotect.$", 

2593 ): 

2594 self.cog.callable_main(["argv0", "-c", "cog4.txt"]) 

2595 with self.assertRaisesRegex( 

2596 CogError, 

2597 r"^cog5.txt\(10\): Output has been edited! Delete old checksum to unprotect.$", 

2598 ): 

2599 self.cog.callable_main(["argv0", "-c", "cog5.txt"]) 

2600 with self.assertRaisesRegex( 

2601 CogError, 

2602 r"^cog6.txt\(6\): Output has been edited! Delete old checksum to unprotect.$", 

2603 ): 

2604 self.cog.callable_main(["argv0", "-c", "cog6.txt"]) 

2605 

2606 def test_argv_isnt_modified(self): 

2607 argv = ["argv0", "-v"] 

2608 orig_argv = argv[:] 

2609 self.cog.callable_main(argv) 

2610 self.assertEqual(argv, orig_argv) 

2611 

2612 

2613class CustomMarkerTests(TestCaseWithTempDir): 

2614 def test_customer_markers(self): 

2615 d = { 

2616 "test.cog": """\ 

2617 //{{ 

2618 cog.outl("void %s();" % "MyFunction") 

2619 //}} 

2620 //{{end}} 

2621 """, 

2622 "test.out": """\ 

2623 //{{ 

2624 cog.outl("void %s();" % "MyFunction") 

2625 //}} 

2626 void MyFunction(); 

2627 //{{end}} 

2628 """, 

2629 } 

2630 

2631 make_files(d) 

2632 self.cog.callable_main(["argv0", "-r", "--markers={{ }} {{end}}", "test.cog"]) 

2633 self.assertFilesSame("test.cog", "test.out") 

2634 

2635 def test_truly_wacky_markers(self): 

2636 # Make sure the markers are properly re-escaped. 

2637 d = { 

2638 "test.cog": """\ 

2639 //**( 

2640 cog.outl("void %s();" % "MyFunction") 

2641 //**) 

2642 //**(end)** 

2643 """, 

2644 "test.out": """\ 

2645 //**( 

2646 cog.outl("void %s();" % "MyFunction") 

2647 //**) 

2648 void MyFunction(); 

2649 //**(end)** 

2650 """, 

2651 } 

2652 

2653 make_files(d) 

2654 self.cog.callable_main( 

2655 ["argv0", "-r", "--markers=**( **) **(end)**", "test.cog"] 

2656 ) 

2657 self.assertFilesSame("test.cog", "test.out") 

2658 

2659 def test_change_just_one_marker(self): 

2660 d = { 

2661 "test.cog": """\ 

2662 //**( 

2663 cog.outl("void %s();" % "MyFunction") 

2664 //]]] 

2665 //[[[end]]] 

2666 """, 

2667 "test.out": """\ 

2668 //**( 

2669 cog.outl("void %s();" % "MyFunction") 

2670 //]]] 

2671 void MyFunction(); 

2672 //[[[end]]] 

2673 """, 

2674 } 

2675 

2676 make_files(d) 

2677 self.cog.callable_main( 

2678 ["argv0", "-r", "--markers=**( ]]] [[[end]]]", "test.cog"] 

2679 ) 

2680 self.assertFilesSame("test.cog", "test.out") 

2681 

2682 

2683class BlakeTests(TestCaseWithTempDir): 

2684 # Blake Winton's contributions. 

2685 def test_delete_code(self): 

2686 # -o sets the output file. 

2687 d = { 

2688 "test.cog": """\ 

2689 // This is my C++ file. 

2690 //[[[cog 

2691 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing'] 

2692 for fn in fnames: 

2693 cog.outl("void %s();" % fn) 

2694 //]]] 

2695 Some Sample Code Here 

2696 //[[[end]]]Data Data 

2697 And Some More 

2698 """, 

2699 "test.out": """\ 

2700 // This is my C++ file. 

2701 void DoSomething(); 

2702 void DoAnotherThing(); 

2703 void DoLastThing(); 

2704 And Some More 

2705 """, 

2706 } 

2707 

2708 make_files(d) 

2709 self.cog.callable_main(["argv0", "-d", "-o", "test.cogged", "test.cog"]) 

2710 self.assertFilesSame("test.cogged", "test.out") 

2711 

2712 def test_delete_code_with_dash_r_fails(self): 

2713 d = { 

2714 "test.cog": """\ 

2715 // This is my C++ file. 

2716 """ 

2717 } 

2718 

2719 make_files(d) 

2720 with self.assertRaisesRegex( 

2721 CogUsageError, 

2722 r"^Can't use -d with -r \(or you would delete all your source!\)$", 

2723 ): 

2724 self.cog.callable_main(["argv0", "-r", "-d", "test.cog"]) 

2725 

2726 def test_setting_globals(self): 

2727 # Blake Winton contributed a way to set the globals that will be used in 

2728 # processFile(). 

2729 d = { 

2730 "test.cog": """\ 

2731 // This is my C++ file. 

2732 //[[[cog 

2733 for fn in fnames: 

2734 cog.outl("void %s();" % fn) 

2735 //]]] 

2736 Some Sample Code Here 

2737 //[[[end]]]""", 

2738 "test.out": """\ 

2739 // This is my C++ file. 

2740 void DoBlake(); 

2741 void DoWinton(); 

2742 void DoContribution(); 

2743 """, 

2744 } 

2745 

2746 make_files(d) 

2747 globals = {} 

2748 globals["fnames"] = ["DoBlake", "DoWinton", "DoContribution"] 

2749 self.cog.options.delete_code = True 

2750 self.cog.process_file("test.cog", "test.cogged", globals=globals) 

2751 self.assertFilesSame("test.cogged", "test.out") 

2752 

2753 

2754class ErrorCallTests(TestCaseWithTempDir): 

2755 def test_error_call_has_no_traceback(self): 

2756 # Test that cog.error() doesn't show a traceback. 

2757 d = { 

2758 "error.cog": """\ 

2759 //[[[cog 

2760 cog.error("Something Bad!") 

2761 //]]] 

2762 //[[[end]]] 

2763 """, 

2764 } 

2765 

2766 make_files(d) 

2767 self.cog.main(["argv0", "-r", "error.cog"]) 

2768 output = self.output.getvalue() 

2769 self.assertEqual(output, "Cogging error.cog\nError: Something Bad!\n") 

2770 

2771 def test_real_error_has_traceback(self): 

2772 # Test that a genuine error does show a traceback. 

2773 d = { 

2774 "error.cog": """\ 

2775 //[[[cog 

2776 raise RuntimeError("Hey!") 

2777 //]]] 

2778 //[[[end]]] 

2779 """, 

2780 } 

2781 

2782 make_files(d) 

2783 self.cog.main(["argv0", "-r", "error.cog"]) 

2784 output = self.output.getvalue() 

2785 msg = "Actual output:\n" + output 

2786 self.assertTrue( 

2787 output.startswith("Cogging error.cog\nTraceback (most recent"), msg 

2788 ) 

2789 self.assertIn("RuntimeError: Hey!", output) 

2790 

2791 

2792class HashHandlerTests(TestCase): 

2793 """Test cases for HashHandler functionality.""" 

2794 

2795 def setUp(self): 

2796 self.handler = HashHandler("[[[end]]]") 

2797 

2798 def test_validate_hash_with_base64_mismatch(self): 

2799 # Test the base64 validation branch with a mismatch 

2800 line = "//[[[end]]] (sum: wronghas12)" # 10 chars to match regex 

2801 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346" 

2802 

2803 with self.assertRaises(ValueError) as cm: 

2804 self.handler.validate_hash(line, expected_hash) 

2805 self.assertEqual( 

2806 str(cm.exception), 

2807 "Output has been edited! Delete old checksum to unprotect.", 

2808 ) 

2809 

2810 def test_validate_hash_with_base64_match(self): 

2811 # Test the base64 validation branch with a match 

2812 line = "//[[[end]]] (sum: qFQJguWta5)" 

2813 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346" 

2814 

2815 # Should not raise an exception 

2816 result = self.handler.validate_hash(line, expected_hash) 

2817 self.assertTrue(result) 

2818 

2819 

2820# Things not yet tested: 

2821# - A bad -w command (currently fails silently).