Coverage for cogapp / test_cogapp.py: 99.36%

912 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-01-25 07:14 -0500

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 __version__, main 

18from .hashhandler import HashHandler 

19from .makefiles import make_files 

20from .options import Markers, description 

21from .whiteutils import reindent_block 

22 

23 

24class CogTestsInMemory(TestCase): 

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

26 

27 def test_no_cog(self): 

28 strings = [ 

29 "", 

30 " ", 

31 " \t \t \tx", 

32 "hello", 

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

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

35 ] 

36 for s in strings: 

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

38 

39 def test_simple(self): 

40 infile = """\ 

41 Some text. 

42 //[[[cog 

43 import cog 

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

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

46 //]]] 

47 gobbledegook. 

48 //[[[end]]] 

49 epilogue. 

50 """ 

51 

52 outfile = """\ 

53 Some text. 

54 //[[[cog 

55 import cog 

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

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

58 //]]] 

59 This is line one 

60 

61 This is line two 

62 //[[[end]]] 

63 epilogue. 

64 """ 

65 

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

67 

68 def test_empty_cog(self): 

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

70 # but it works. 

71 infile = """\ 

72 hello 

73 //[[[cog 

74 //]]] 

75 //[[[end]]] 

76 goodbye 

77 """ 

78 

79 infile = reindent_block(infile) 

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

81 

82 def test_multiple_cogs(self): 

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

84 infile = """\ 

85 //[[[cog 

86 cog.out("chunk1") 

87 //]]] 

88 chunk1 

89 //[[[end]]] 

90 //[[[cog 

91 cog.out("chunk2") 

92 //]]] 

93 chunk2 

94 //[[[end]]] 

95 between chunks 

96 //[[[cog 

97 cog.out("chunk3") 

98 //]]] 

99 chunk3 

100 //[[[end]]] 

101 """ 

102 

103 infile = reindent_block(infile) 

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

105 

106 def test_trim_blank_lines(self): 

107 infile = """\ 

108 //[[[cog 

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

110 cog.out(''' 

111 This is line two 

112 ''', dedent=True, trimblanklines=True) 

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

114 //]]] 

115 This is line one 

116 This is line two 

117 This is line three 

118 //[[[end]]] 

119 """ 

120 

121 infile = reindent_block(infile) 

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

123 

124 def test_trim_empty_blank_lines(self): 

125 infile = """\ 

126 //[[[cog 

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

128 cog.out(''' 

129 This is line two 

130 ''', dedent=True, trimblanklines=True) 

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

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

133 //]]] 

134 This is line one 

135 This is line two 

136 This is line three 

137 //[[[end]]] 

138 """ 

139 

140 infile = reindent_block(infile) 

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

142 

143 def test_trim_blank_lines_with_last_partial(self): 

144 infile = """\ 

145 //[[[cog 

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

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

148 //]]] 

149 This is line one 

150 Line two 

151 Line three 

152 //[[[end]]] 

153 """ 

154 

155 infile = reindent_block(infile) 

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

157 

158 def test_cog_out_dedent(self): 

159 infile = """\ 

160 //[[[cog 

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

162 cog.out(''' 

163 This is dedent=True 1 

164 This is dedent=True 2 

165 ''', dedent=True, trimblanklines=True) 

166 cog.out(''' 

167 This is dedent=False 1 

168 This is dedent=False 2 

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

170 cog.out(''' 

171 This is dedent=default 1 

172 This is dedent=default 2 

173 ''', trimblanklines=True) 

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

175 //]]] 

176 This is the first line 

177 This is dedent=True 1 

178 This is dedent=True 2 

179 This is dedent=False 1 

180 This is dedent=False 2 

181 This is dedent=default 1 

182 This is dedent=default 2 

183 This is the last line 

184 //[[[end]]] 

185 """ 

186 

187 infile = reindent_block(infile) 

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

189 

190 def test22_end_of_line(self): 

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

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

193 infile = """\ 

194 //[[[cog 

195 import cog 

196 for i in range(3): 

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

198 //]]] 

199 0 

200 1 

201 2 

202 //[[[end]]] 

203 """ 

204 

205 infile = reindent_block(infile) 

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

207 

208 def test_indented_code(self): 

209 infile = """\ 

210 first line 

211 [[[cog 

212 import cog 

213 for i in range(3): 

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

215 ]]] 

216 xx0 

217 xx1 

218 xx2 

219 [[[end]]] 

220 last line 

221 """ 

222 

223 infile = reindent_block(infile) 

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

225 

226 def test_prefixed_code(self): 

227 infile = """\ 

228 --[[[cog 

229 --import cog 

230 --for i in range(3): 

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

232 --]]] 

233 xx0 

234 xx1 

235 xx2 

236 --[[[end]]] 

237 """ 

238 

239 infile = reindent_block(infile) 

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

241 

242 def test_prefixed_indented_code(self): 

243 infile = """\ 

244 prologue 

245 --[[[cog 

246 -- import cog 

247 -- for i in range(3): 

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

249 --]]] 

250 xy0 

251 xy1 

252 xy2 

253 --[[[end]]] 

254 """ 

255 

256 infile = reindent_block(infile) 

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

258 

259 def test_bogus_prefix_match(self): 

260 infile = """\ 

261 prologue 

262 #[[[cog 

263 import cog 

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

265 for i in range(3): 

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

267 #]]] 

268 xy0 

269 xy1 

270 xy2 

271 #[[[end]]] 

272 """ 

273 

274 infile = reindent_block(infile) 

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

276 

277 def test_no_final_newline(self): 

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

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

280 infile = """\ 

281 prologue 

282 [[[cog 

283 import cog 

284 for i in range(3): 

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

286 ]]] 

287 012 

288 [[[end]]] 

289 epilogue 

290 """ 

291 

292 infile = reindent_block(infile) 

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

294 

295 def test_no_output_at_all(self): 

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

297 infile = """\ 

298 prologue 

299 [[[cog 

300 i = 1 

301 ]]] 

302 [[[end]]] 

303 epilogue 

304 """ 

305 

306 infile = reindent_block(infile) 

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

308 

309 def test_purely_blank_line(self): 

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

311 # prefix, that should be OK. 

312 

313 infile = """\ 

314 prologue 

315 [[[cog 

316 import sys 

317 cog.out("Hello") 

318 $ 

319 cog.out("There") 

320 ]]] 

321 HelloThere 

322 [[[end]]] 

323 epilogue 

324 """ 

325 

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

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

328 

329 def test_empty_outl(self): 

330 # Alexander Belchenko suggested the string argument to outl should 

331 # be optional. Does it work? 

332 

333 infile = """\ 

334 prologue 

335 [[[cog 

336 cog.outl("x") 

337 cog.outl() 

338 cog.outl("y") 

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

340 cog.outl(trimblanklines=True) 

341 cog.outl("z") 

342 ]]] 

343 x 

344 

345 y 

346 

347 z 

348 [[[end]]] 

349 epilogue 

350 """ 

351 

352 infile = reindent_block(infile) 

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

354 

355 def test_first_line_num(self): 

356 infile = """\ 

357 fooey 

358 [[[cog 

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

360 ]]] 

361 started at line number 2 

362 [[[end]]] 

363 blah blah 

364 [[[cog 

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

366 ]]] 

367 and again at line 8 

368 [[[end]]] 

369 """ 

370 

371 infile = reindent_block(infile) 

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

373 

374 def test_compact_one_line_code(self): 

375 infile = """\ 

376 first line 

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

378 get rid of this! 

379 [[[end]]] 

380 last line 

381 """ 

382 

383 outfile = """\ 

384 first line 

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

386 hello 81 

387 [[[end]]] 

388 last line 

389 """ 

390 

391 infile = reindent_block(infile) 

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

393 

394 def test_inside_out_compact(self): 

395 infile = """\ 

396 first line 

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

398 get rid of this! 

399 [[[end]]] 

400 last line 

401 """ 

402 with self.assertRaisesRegex( 

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

404 ): 

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

406 

407 def test_sharing_globals(self): 

408 infile = """\ 

409 first line 

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

411 [[[end]]] 

412 more literal junk. 

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

414 [[[end]]] 

415 last line 

416 """ 

417 

418 outfile = """\ 

419 first line 

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

421 [[[end]]] 

422 more literal junk. 

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

424 hey there 

425 [[[end]]] 

426 last line 

427 """ 

428 

429 infile = reindent_block(infile) 

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

431 

432 def test_assert_in_cog_code(self): 

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

434 infile = """\ 

435 [[[cog 

436 assert 1 == 2, "Oops" 

437 ]]] 

438 [[[end]]] 

439 """ 

440 infile = reindent_block(infile) 

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

442 Cog().process_string(infile) 

443 

444 def test_cog_previous(self): 

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

446 infile = """\ 

447 [[[cog 

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

449 cog.out(cog.previous) 

450 cog.outl("Ran again!") 

451 ]]] 

452 Hello there! 

453 [[[end]]] 

454 """ 

455 

456 outfile = """\ 

457 [[[cog 

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

459 cog.out(cog.previous) 

460 cog.outl("Ran again!") 

461 ]]] 

462 Hello there! 

463 Ran again! 

464 [[[end]]] 

465 """ 

466 

467 infile = reindent_block(infile) 

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

469 

470 

471class CogOptionsTests(TestCase): 

472 """Test the CogOptions class.""" 

473 

474 def test_equality(self): 

475 o = CogOptions() 

476 p = CogOptions() 

477 self.assertEqual(o, p) 

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

479 self.assertNotEqual(o, p) 

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

481 self.assertEqual(o, p) 

482 

483 def test_cloning(self): 

484 o = CogOptions() 

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

486 p = o.clone() 

487 self.assertEqual(o, p) 

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

489 self.assertNotEqual(o, p) 

490 q = CogOptions() 

491 q.parse_args( 

492 [ 

493 "-I", 

494 "fooey", 

495 "-I", 

496 "booey", 

497 "-s", 

498 " /*x*/", 

499 "-I", 

500 "huey", 

501 "-D", 

502 "foo=quux", 

503 ] 

504 ) 

505 self.assertEqual(p, q) 

506 

507 def test_combining_flags(self): 

508 # Single-character flags can be combined. 

509 o = CogOptions() 

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

511 p = CogOptions() 

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

513 self.assertEqual(o, p) 

514 

515 def test_markers(self): 

516 o = Markers.from_arg("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("unrecognized arguments: -j", output) 

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

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

815 with self.assertRaisesRegex(CogUsageError, r"^unrecognized arguments: -j$"): 

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 output = self.output.getvalue() 

844 self.assertRegex(output, f"^{re.escape(description)}.*") 

845 

846 def test_dash_h(self): 

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

848 self.produces_help("-h") 

849 self.produces_help("--help") 

850 self.produces_help("-?") 

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

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

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

854 

855 def test_dash_o_and_dash_r(self): 

856 d = { 

857 "cogfile.txt": """\ 

858 # Please run cog 

859 """ 

860 } 

861 

862 make_files(d) 

863 with self.assertRaisesRegex( 

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

865 ): 

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

867 

868 def test_dash_z(self): 

869 d = { 

870 "test.cog": """\ 

871 // This is my C++ file. 

872 //[[[cog 

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

874 for fn in fnames: 

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

876 //]]] 

877 """, 

878 "test.out": """\ 

879 // This is my C++ file. 

880 //[[[cog 

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

882 for fn in fnames: 

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

884 //]]] 

885 void DoSomething(); 

886 void DoAnotherThing(); 

887 void DoLastThing(); 

888 """, 

889 } 

890 

891 make_files(d) 

892 with self.assertRaisesRegex( 

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

894 ): 

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

896 self.new_cog() 

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

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

899 

900 def test_bad_dash_d(self): 

901 with self.assertRaisesRegex( 

902 CogUsageError, r"^argument -D: takes a name=value argument$" 

903 ): 

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

905 with self.assertRaisesRegex( 

906 CogUsageError, r"^argument -D: takes a name=value argument$" 

907 ): 

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

909 

910 def test_bad_markers(self): 

911 with self.assertRaisesRegex( 

912 CogUsageError, 

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

914 ): 

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

916 with self.assertRaisesRegex( 

917 CogUsageError, 

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

919 ): 

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

921 

922 

923class TestMain(TestCaseWithTempDir): 

924 def setUp(self): 

925 super().setUp() 

926 self.old_argv = sys.argv[:] 

927 self.old_stderr = sys.stderr 

928 sys.stderr = io.StringIO() 

929 

930 def tearDown(self): 

931 sys.stderr = self.old_stderr 

932 sys.argv = self.old_argv 

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

934 super().tearDown() 

935 

936 def test_main_function(self): 

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

938 ret = main() 

939 self.assertEqual(ret, 2) 

940 stderr = sys.stderr.getvalue() 

941 self.assertEqual(stderr, "unrecognized arguments: -Z\n(for help use --help)\n") 

942 

943 files = { 

944 "test.cog": """\ 

945 //[[[cog 

946 def func(): 

947 import mycode 

948 mycode.boom() 

949 //]]] 

950 //[[[end]]] 

951 ----- 

952 //[[[cog 

953 func() 

954 //]]] 

955 //[[[end]]] 

956 """, 

957 "mycode.py": """\ 

958 def boom(): 

959 [][0] 

960 """, 

961 } 

962 

963 def test_error_report(self): 

964 self.check_error_report() 

965 

966 def test_error_report_with_prologue(self): 

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

968 

969 def check_error_report(self, *args): 

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

971 make_files(self.files) 

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

973 main() 

974 expected = reindent_block("""\ 

975 Traceback (most recent call last): 

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

977 func() 

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

979 mycode.boom() 

980 File "MYCODE", line 2, in boom 

981 [][0] 

982 IndexError: list index out of range 

983 """) 

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

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

986 

987 def test_error_in_prologue(self): 

988 make_files(self.files) 

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

990 main() 

991 expected = reindent_block("""\ 

992 Traceback (most recent call last): 

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

994 import mycode; mycode.boom() 

995 File "MYCODE", line 2, in boom 

996 [][0] 

997 IndexError: list index out of range 

998 """) 

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

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

1001 

1002 

1003class TestFileHandling(TestCaseWithTempDir): 

1004 def test_simple(self): 

1005 d = { 

1006 "test.cog": """\ 

1007 // This is my C++ file. 

1008 //[[[cog 

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

1010 for fn in fnames: 

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

1012 //]]] 

1013 //[[[end]]] 

1014 """, 

1015 "test.out": """\ 

1016 // This is my C++ file. 

1017 //[[[cog 

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

1019 for fn in fnames: 

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

1021 //]]] 

1022 void DoSomething(); 

1023 void DoAnotherThing(); 

1024 void DoLastThing(); 

1025 //[[[end]]] 

1026 """, 

1027 } 

1028 

1029 make_files(d) 

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

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

1032 output = self.output.getvalue() 

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

1034 

1035 def test_print_output(self): 

1036 d = { 

1037 "test.cog": """\ 

1038 // This is my C++ file. 

1039 //[[[cog 

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

1041 for fn in fnames: 

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

1043 //]]] 

1044 //[[[end]]] 

1045 """, 

1046 "test.out": """\ 

1047 // This is my C++ file. 

1048 //[[[cog 

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

1050 for fn in fnames: 

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

1052 //]]] 

1053 void DoSomething(); 

1054 void DoAnotherThing(); 

1055 void DoLastThing(); 

1056 //[[[end]]] 

1057 """, 

1058 } 

1059 

1060 make_files(d) 

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

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

1063 output = self.output.getvalue() 

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

1065 

1066 def test_wildcards(self): 

1067 d = { 

1068 "test.cog": """\ 

1069 // This is my C++ file. 

1070 //[[[cog 

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

1072 for fn in fnames: 

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

1074 //]]] 

1075 //[[[end]]] 

1076 """, 

1077 "test2.cog": """\ 

1078 // This is my C++ file. 

1079 //[[[cog 

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

1081 for fn in fnames: 

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

1083 //]]] 

1084 //[[[end]]] 

1085 """, 

1086 "test.out": """\ 

1087 // This is my C++ file. 

1088 //[[[cog 

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

1090 for fn in fnames: 

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

1092 //]]] 

1093 void DoSomething(); 

1094 void DoAnotherThing(); 

1095 void DoLastThing(); 

1096 //[[[end]]] 

1097 """, 

1098 "not_this_one.cog": """\ 

1099 // This is my C++ file. 

1100 //[[[cog 

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

1102 for fn in fnames: 

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

1104 //]]] 

1105 //[[[end]]] 

1106 """, 

1107 "not_this_one.out": """\ 

1108 // This is my C++ file. 

1109 //[[[cog 

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

1111 for fn in fnames: 

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

1113 //]]] 

1114 //[[[end]]] 

1115 """, 

1116 } 

1117 

1118 make_files(d) 

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

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

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

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

1123 output = self.output.getvalue() 

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

1125 

1126 def test_output_file(self): 

1127 # -o sets the output file. 

1128 d = { 

1129 "test.cog": """\ 

1130 // This is my C++ file. 

1131 //[[[cog 

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

1133 for fn in fnames: 

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

1135 //]]] 

1136 //[[[end]]] 

1137 """, 

1138 "test.out": """\ 

1139 // This is my C++ file. 

1140 //[[[cog 

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

1142 for fn in fnames: 

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

1144 //]]] 

1145 void DoSomething(); 

1146 void DoAnotherThing(); 

1147 void DoLastThing(); 

1148 //[[[end]]] 

1149 """, 

1150 } 

1151 

1152 make_files(d) 

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

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

1155 

1156 def test_at_file(self): 

1157 d = { 

1158 "one.cog": """\ 

1159 //[[[cog 

1160 cog.outl("hello world") 

1161 //]]] 

1162 //[[[end]]] 

1163 """, 

1164 "one.out": """\ 

1165 //[[[cog 

1166 cog.outl("hello world") 

1167 //]]] 

1168 hello world 

1169 //[[[end]]] 

1170 """, 

1171 "two.cog": """\ 

1172 //[[[cog 

1173 cog.outl("goodbye cruel world") 

1174 //]]] 

1175 //[[[end]]] 

1176 """, 

1177 "two.out": """\ 

1178 //[[[cog 

1179 cog.outl("goodbye cruel world") 

1180 //]]] 

1181 goodbye cruel world 

1182 //[[[end]]] 

1183 """, 

1184 "cogfiles.txt": """\ 

1185 # Please run cog 

1186 one.cog 

1187 

1188 two.cog 

1189 """, 

1190 } 

1191 

1192 make_files(d) 

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

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

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

1196 output = self.output.getvalue() 

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

1198 

1199 def test_nested_at_file(self): 

1200 d = { 

1201 "one.cog": """\ 

1202 //[[[cog 

1203 cog.outl("hello world") 

1204 //]]] 

1205 //[[[end]]] 

1206 """, 

1207 "one.out": """\ 

1208 //[[[cog 

1209 cog.outl("hello world") 

1210 //]]] 

1211 hello world 

1212 //[[[end]]] 

1213 """, 

1214 "two.cog": """\ 

1215 //[[[cog 

1216 cog.outl("goodbye cruel world") 

1217 //]]] 

1218 //[[[end]]] 

1219 """, 

1220 "two.out": """\ 

1221 //[[[cog 

1222 cog.outl("goodbye cruel world") 

1223 //]]] 

1224 goodbye cruel world 

1225 //[[[end]]] 

1226 """, 

1227 "cogfiles.txt": """\ 

1228 # Please run cog 

1229 one.cog 

1230 @cogfiles2.txt 

1231 """, 

1232 "cogfiles2.txt": """\ 

1233 # This one too, please. 

1234 two.cog 

1235 """, 

1236 } 

1237 

1238 make_files(d) 

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

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

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

1242 output = self.output.getvalue() 

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

1244 

1245 def test_at_file_with_args(self): 

1246 d = { 

1247 "both.cog": """\ 

1248 //[[[cog 

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

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

1251 //]]] 

1252 //[[[end]]] 

1253 """, 

1254 "one.out": """\ 

1255 //[[[cog 

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

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

1258 //]]] 

1259 one: True // ONE 

1260 two: False // ONE 

1261 //[[[end]]] 

1262 """, 

1263 "two.out": """\ 

1264 //[[[cog 

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

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

1267 //]]] 

1268 one: False // TWO 

1269 two: True // TWO 

1270 //[[[end]]] 

1271 """, 

1272 "cogfiles.txt": """\ 

1273 # Please run cog 

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

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

1276 """, 

1277 } 

1278 

1279 make_files(d) 

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

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

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

1283 

1284 def test_at_file_with_bad_arg_combo(self): 

1285 d = { 

1286 "both.cog": """\ 

1287 //[[[cog 

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

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

1290 //]]] 

1291 //[[[end]]] 

1292 """, 

1293 "cogfiles.txt": """\ 

1294 # Please run cog 

1295 both.cog 

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

1297 """, 

1298 } 

1299 

1300 make_files(d) 

1301 with self.assertRaisesRegex( 

1302 CogUsageError, 

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

1304 ): 

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

1306 

1307 def test_at_file_with_tricky_filenames(self): 

1308 def fix_backslashes(files_txt): 

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

1310 if sys.platform != "win32": 1310 ↛ 1312line 1310 didn't jump to line 1312 because the condition on line 1310 was always true

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

1312 return files_txt 

1313 

1314 d = { 

1315 "one 1.cog": """\ 

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

1317 """, 

1318 "one.out": """\ 

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

1320 hello world //xxx 

1321 """, 

1322 "subdir": { 

1323 "subback.cog": """\ 

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

1325 """, 

1326 "subfwd.cog": """\ 

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

1328 """, 

1329 }, 

1330 "subback.out": """\ 

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

1332 down deep with backslashes //yyy 

1333 """, 

1334 "subfwd.out": """\ 

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

1336 down deep with slashes //zzz 

1337 """, 

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

1339 # Please run cog 

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

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

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

1343 """), 

1344 } 

1345 

1346 make_files(d) 

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

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

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

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

1351 

1352 def test_amp_file(self): 

1353 d = { 

1354 "code": { 

1355 "files_to_cog": """\ 

1356 # A locally resolved file name. 

1357 test.cog 

1358 """, 

1359 "test.cog": """\ 

1360 //[[[cog 

1361 import myampsubmodule 

1362 //]]] 

1363 //[[[end]]] 

1364 """, 

1365 "test.out": """\ 

1366 //[[[cog 

1367 import myampsubmodule 

1368 //]]] 

1369 Hello from myampsubmodule 

1370 //[[[end]]] 

1371 """, 

1372 "myampsubmodule.py": """\ 

1373 import cog 

1374 cog.outl("Hello from myampsubmodule") 

1375 """, 

1376 } 

1377 } 

1378 

1379 make_files(d) 

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

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

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

1383 

1384 def run_with_verbosity(self, verbosity): 

1385 d = { 

1386 "unchanged.cog": """\ 

1387 //[[[cog 

1388 cog.outl("hello world") 

1389 //]]] 

1390 hello world 

1391 //[[[end]]] 

1392 """, 

1393 "changed.cog": """\ 

1394 //[[[cog 

1395 cog.outl("goodbye cruel world") 

1396 //]]] 

1397 //[[[end]]] 

1398 """, 

1399 "cogfiles.txt": """\ 

1400 unchanged.cog 

1401 changed.cog 

1402 """, 

1403 } 

1404 

1405 make_files(d) 

1406 self.cog.callable_main( 

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

1408 ) 

1409 output = self.output.getvalue() 

1410 return output 

1411 

1412 def test_verbosity0(self): 

1413 output = self.run_with_verbosity("0") 

1414 self.assertEqual(output, "") 

1415 

1416 def test_verbosity1(self): 

1417 output = self.run_with_verbosity("1") 

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

1419 

1420 def test_verbosity2(self): 

1421 output = self.run_with_verbosity("2") 

1422 self.assertEqual( 

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

1424 ) 

1425 

1426 def test_change_dir(self): 

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

1428 d = { 

1429 "sub": { 

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

1431 }, 

1432 "test.cog": """\ 

1433 //[[[cog 

1434 import os 

1435 os.chdir("sub") 

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

1437 //]]] 

1438 //[[[end]]] 

1439 """, 

1440 "test.out": """\ 

1441 //[[[cog 

1442 import os 

1443 os.chdir("sub") 

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

1445 //]]] 

1446 Hello! 

1447 //[[[end]]] 

1448 """, 

1449 } 

1450 

1451 make_files(d) 

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

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

1454 output = self.output.getvalue() 

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

1456 

1457 

1458class CogTestLineEndings(TestCaseWithTempDir): 

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

1460 

1461 lines_in = [ 

1462 "Some text.", 

1463 "//[[[cog", 

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

1465 "//]]]", 

1466 "gobbledegook.", 

1467 "//[[[end]]]", 

1468 "epilogue.", 

1469 "", 

1470 ] 

1471 

1472 lines_out = [ 

1473 "Some text.", 

1474 "//[[[cog", 

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

1476 "//]]]", 

1477 "Cog text", 

1478 "//[[[end]]]", 

1479 "epilogue.", 

1480 "", 

1481 ] 

1482 

1483 def test_output_native_eol(self): 

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

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

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

1487 

1488 def test_output_lf_eol(self): 

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

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

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

1492 

1493 def test_replace_native_eol(self): 

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

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

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

1497 

1498 def test_replace_lf_eol(self): 

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

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

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

1502 

1503 

1504class CogTestCharacterEncoding(TestCaseWithTempDir): 

1505 def test_simple(self): 

1506 d = { 

1507 "test.cog": b"""\ 

1508 // This is my C++ file. 

1509 //[[[cog 

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

1511 //]]] 

1512 //[[[end]]] 

1513 """, 

1514 "test.out": b"""\ 

1515 // This is my C++ file. 

1516 //[[[cog 

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

1518 //]]] 

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

1520 //[[[end]]] 

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

1522 } 

1523 

1524 make_files(d) 

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

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

1527 output = self.output.getvalue() 

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

1529 

1530 def test_file_encoding_option(self): 

1531 d = { 

1532 "test.cog": b"""\ 

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

1534 //[[[cog 

1535 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") 

1536 //]]] 

1537 //[[[end]]] 

1538 """, 

1539 "test.out": b"""\ 

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

1541 //[[[cog 

1542 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") 

1543 //]]] 

1544 \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 

1545 //[[[end]]] 

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

1547 } 

1548 

1549 make_files(d) 

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

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

1552 output = self.output.getvalue() 

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

1554 

1555 

1556class TestCaseWithImports(TestCaseWithTempDir): 

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

1558 

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

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

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

1562 

1563 """ 

1564 

1565 def setUp(self): 

1566 super().setUp() 

1567 self.sysmodulekeys = list(sys.modules) 

1568 

1569 def tearDown(self): 

1570 modstoscrub = [ 

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

1572 ] 

1573 for modname in modstoscrub: 

1574 del sys.modules[modname] 

1575 super().tearDown() 

1576 

1577 

1578class CogIncludeTests(TestCaseWithImports): 

1579 dincludes = { 

1580 "test.cog": """\ 

1581 //[[[cog 

1582 import mymodule 

1583 //]]] 

1584 //[[[end]]] 

1585 """, 

1586 "test.out": """\ 

1587 //[[[cog 

1588 import mymodule 

1589 //]]] 

1590 Hello from mymodule 

1591 //[[[end]]] 

1592 """, 

1593 "test2.out": """\ 

1594 //[[[cog 

1595 import mymodule 

1596 //]]] 

1597 Hello from mymodule in inc2 

1598 //[[[end]]] 

1599 """, 

1600 "include": { 

1601 "mymodule.py": """\ 

1602 import cog 

1603 cog.outl("Hello from mymodule") 

1604 """ 

1605 }, 

1606 "inc2": { 

1607 "mymodule.py": """\ 

1608 import cog 

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

1610 """ 

1611 }, 

1612 "inc3": { 

1613 "someothermodule.py": """\ 

1614 import cog 

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

1616 """ 

1617 }, 

1618 } 

1619 

1620 def test_need_include_path(self): 

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

1622 make_files(self.dincludes) 

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

1624 with self.assertRaisesRegex(CogUserException, msg): 

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

1626 

1627 def test_include_path(self): 

1628 # Test that -I adds include directories properly. 

1629 make_files(self.dincludes) 

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

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

1632 

1633 def test_two_include_paths(self): 

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

1635 make_files(self.dincludes) 

1636 self.cog.callable_main( 

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

1638 ) 

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

1640 

1641 def test_two_include_paths2(self): 

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

1643 make_files(self.dincludes) 

1644 self.cog.callable_main( 

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

1646 ) 

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

1648 

1649 def test_useless_include_path(self): 

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

1651 make_files(self.dincludes) 

1652 self.cog.callable_main( 

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

1654 ) 

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

1656 

1657 def test_sys_path_is_unchanged(self): 

1658 d = { 

1659 "bad.cog": """\ 

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

1661 //[[[end]]] 

1662 """, 

1663 "good.cog": """\ 

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

1665 //[[[end]]] 

1666 """, 

1667 } 

1668 

1669 make_files(d) 

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

1671 oldsyspath = sys.path[:] 

1672 self.new_cog() 

1673 self.assertEqual(oldsyspath, sys.path) 

1674 # Is it unchanged for a successful run? 

1675 self.new_cog() 

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

1677 self.assertEqual(oldsyspath, sys.path) 

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

1679 self.new_cog() 

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

1681 self.assertEqual(oldsyspath, sys.path) 

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

1683 self.new_cog() 

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

1685 self.assertEqual(oldsyspath, sys.path) 

1686 # Is it unchanged for a failed run? 

1687 self.new_cog() 

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

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

1690 self.assertEqual(oldsyspath, sys.path) 

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

1692 self.new_cog() 

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

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

1695 self.assertEqual(oldsyspath, sys.path) 

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

1697 self.new_cog() 

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

1699 self.cog.callable_main( 

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

1701 ) 

1702 self.assertEqual(oldsyspath, sys.path) 

1703 

1704 def test_sub_directories(self): 

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

1706 

1707 d = { 

1708 "code": { 

1709 "test.cog": """\ 

1710 //[[[cog 

1711 import mysubmodule 

1712 //]]] 

1713 //[[[end]]] 

1714 """, 

1715 "test.out": """\ 

1716 //[[[cog 

1717 import mysubmodule 

1718 //]]] 

1719 Hello from mysubmodule 

1720 //[[[end]]] 

1721 """, 

1722 "mysubmodule.py": """\ 

1723 import cog 

1724 cog.outl("Hello from mysubmodule") 

1725 """, 

1726 } 

1727 } 

1728 

1729 make_files(d) 

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

1731 # auto-include the current directory 

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

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

1734 

1735 

1736class CogTestsInFiles(TestCaseWithTempDir): 

1737 def test_warn_if_no_cog_code(self): 

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

1739 d = { 

1740 "with.cog": """\ 

1741 //[[[cog 

1742 cog.outl("hello world") 

1743 //]]] 

1744 hello world 

1745 //[[[end]]] 

1746 """, 

1747 "without.cog": """\ 

1748 There's no cog 

1749 code in this file. 

1750 """, 

1751 } 

1752 

1753 make_files(d) 

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

1755 output = self.output.getvalue() 

1756 self.assertNotIn("Warning", output) 

1757 self.new_cog() 

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

1759 output = self.output.getvalue() 

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

1761 self.new_cog() 

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

1763 output = self.output.getvalue() 

1764 self.assertNotIn("Warning", output) 

1765 

1766 def test_file_name_props(self): 

1767 d = { 

1768 "cog1.txt": """\ 

1769 //[[[cog 

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

1771 //]]] 

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

1773 [[[end]]] 

1774 """, 

1775 "cog1.out": """\ 

1776 //[[[cog 

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

1778 //]]] 

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

1780 [[[end]]] 

1781 """, 

1782 "cog1out.out": """\ 

1783 //[[[cog 

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

1785 //]]] 

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

1787 [[[end]]] 

1788 """, 

1789 } 

1790 

1791 make_files(d) 

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

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

1794 self.new_cog() 

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

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

1797 

1798 def test_globals_dont_cross_files(self): 

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

1800 d = { 

1801 "one.cog": """\ 

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

1803 //[[[end]]] 

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

1805 //[[[end]]] 

1806 """, 

1807 "one.out": """\ 

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

1809 //[[[end]]] 

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

1811 This was set in one.cog 

1812 //[[[end]]] 

1813 """, 

1814 "two.cog": """\ 

1815 //[[[cog 

1816 try: 

1817 cog.outl(s) 

1818 except NameError: 

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

1820 //]]] 

1821 //[[[end]]] 

1822 """, 

1823 "two.out": """\ 

1824 //[[[cog 

1825 try: 

1826 cog.outl(s) 

1827 except NameError: 

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

1829 //]]] 

1830 s isn't set! 

1831 //[[[end]]] 

1832 """, 

1833 "cogfiles.txt": """\ 

1834 # Please run cog 

1835 one.cog 

1836 

1837 two.cog 

1838 """, 

1839 } 

1840 

1841 make_files(d) 

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

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

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

1845 output = self.output.getvalue() 

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

1847 

1848 def test_remove_generated_output(self): 

1849 d = { 

1850 "cog1.txt": """\ 

1851 //[[[cog 

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

1853 //]]] 

1854 This line was generated. 

1855 //[[[end]]] 

1856 This line was not. 

1857 """, 

1858 "cog1.out": """\ 

1859 //[[[cog 

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

1861 //]]] 

1862 //[[[end]]] 

1863 This line was not. 

1864 """, 

1865 "cog1.out2": """\ 

1866 //[[[cog 

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

1868 //]]] 

1869 This line was generated. 

1870 //[[[end]]] 

1871 This line was not. 

1872 """, 

1873 } 

1874 

1875 make_files(d) 

1876 # Remove generated output. 

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

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

1879 self.new_cog() 

1880 # Regenerate the generated output. 

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

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

1883 self.new_cog() 

1884 # Remove the generated output again. 

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

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

1887 

1888 def test_msg_call(self): 

1889 infile = """\ 

1890 #[[[cog 

1891 cog.msg("Hello there!") 

1892 #]]] 

1893 #[[[end]]] 

1894 """ 

1895 infile = reindent_block(infile) 

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

1897 output = self.output.getvalue() 

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

1899 

1900 def test_error_message_has_no_traceback(self): 

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

1902 

1903 d = { 

1904 "cog1.txt": """\ 

1905 //[[[cog 

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

1907 cog.outl("generated by cog") 

1908 cog.outl("blah blah.") 

1909 //]]] 

1910 Xhis line was newly 

1911 generated by cog 

1912 blah blah. 

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

1914 """, 

1915 } 

1916 

1917 make_files(d) 

1918 stderr = io.StringIO() 

1919 self.cog.set_output(stderr=stderr) 

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

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

1922 self.assertEqual( 

1923 stderr.getvalue(), 

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

1925 ) 

1926 

1927 def test_dash_d(self): 

1928 d = { 

1929 "test.cog": """\ 

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

1931 --[[[end]]] 

1932 """, 

1933 "test.kablooey": """\ 

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

1935 Defined fooey as kablooey 

1936 --[[[end]]] 

1937 """, 

1938 "test.einstein": """\ 

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

1940 Defined fooey as e=mc2 

1941 --[[[end]]] 

1942 """, 

1943 } 

1944 

1945 make_files(d) 

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

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

1948 make_files(d) 

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

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

1951 make_files(d) 

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

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

1954 make_files(d) 

1955 self.cog.callable_main( 

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

1957 ) 

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

1959 make_files(d) 

1960 self.cog.callable_main( 

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

1962 ) 

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

1964 make_files(d) 

1965 self.cog.callable_main( 

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

1967 ) 

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

1969 

1970 def test_output_to_stdout(self): 

1971 d = { 

1972 "test.cog": """\ 

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

1974 --[[[end]]] 

1975 """ 

1976 } 

1977 

1978 make_files(d) 

1979 stderr = io.StringIO() 

1980 self.cog.set_output(stderr=stderr) 

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

1982 output = self.output.getvalue() 

1983 outerr = stderr.getvalue() 

1984 self.assertEqual( 

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

1986 ) 

1987 self.assertEqual(outerr, "") 

1988 

1989 def test_read_from_stdin(self): 

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

1991 

1992 def restore_stdin(old_stdin): 

1993 sys.stdin = old_stdin 

1994 

1995 self.addCleanup(restore_stdin, sys.stdin) 

1996 sys.stdin = stdin 

1997 

1998 stderr = io.StringIO() 

1999 self.cog.set_output(stderr=stderr) 

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

2001 output = self.output.getvalue() 

2002 outerr = stderr.getvalue() 

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

2004 self.assertEqual(outerr, "") 

2005 

2006 def test_suffix_output_lines(self): 

2007 d = { 

2008 "test.cog": """\ 

2009 Hey there. 

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

2011 ;[[[end]]] 

2012 Good bye. 

2013 """, 

2014 "test.out": """\ 

2015 Hey there. 

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

2017 a (foo) 

2018 b (foo) 

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

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

2021 """ 

2022 c (foo) 

2023 ;[[[end]]] 

2024 Good bye. 

2025 """, 

2026 } 

2027 

2028 make_files(d) 

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

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

2031 

2032 def test_empty_suffix(self): 

2033 d = { 

2034 "test.cog": """\ 

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

2036 ;[[[end]]] 

2037 """, 

2038 "test.out": """\ 

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

2040 a 

2041 b 

2042 c 

2043 ;[[[end]]] 

2044 """, 

2045 } 

2046 

2047 make_files(d) 

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

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

2050 

2051 def test_hellish_suffix(self): 

2052 d = { 

2053 "test.cog": """\ 

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

2055 """, 

2056 "test.out": """\ 

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

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

2059 

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

2061 """, 

2062 } 

2063 

2064 make_files(d) 

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

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

2067 

2068 def test_prologue(self): 

2069 d = { 

2070 "test.cog": """\ 

2071 Some text. 

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

2073 //[[[end]]] 

2074 epilogue. 

2075 """, 

2076 "test.out": """\ 

2077 Some text. 

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

2079 1.4142135623 

2080 //[[[end]]] 

2081 epilogue. 

2082 """, 

2083 } 

2084 

2085 make_files(d) 

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

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

2088 

2089 def test_threads(self): 

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

2091 # different threads. 

2092 numthreads = 20 

2093 

2094 d = {} 

2095 for i in range(numthreads): 

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

2097 "x\n" * i 

2098 + "[[[cog\n" 

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

2100 + "]]]\n" 

2101 + "[[[end]]]\n" 

2102 ) 

2103 make_files(d) 

2104 

2105 results = [] 

2106 

2107 def thread_main(num): 

2108 try: 

2109 ret = Cog().main( 

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

2111 ) 

2112 assert ret == 0 

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

2114 results.append(exc) 

2115 else: 

2116 results.append(None) 

2117 

2118 ts = [ 

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

2120 ] 

2121 for t in ts: 

2122 t.start() 

2123 for t in ts: 

2124 t.join() 

2125 assert results == [None] * numthreads 

2126 

2127 

2128class CheckTests(TestCaseWithTempDir): 

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

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

2131 print(self.output.getvalue()) 

2132 self.assertEqual(status, actual_status) 

2133 

2134 def assert_made_files_unchanged(self, d): 

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

2136 content = reindent_block(content) 

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

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

2139 self.assertFileContent(name, content) 

2140 

2141 def test_check_no_cog(self): 

2142 d = { 

2143 "hello.txt": """\ 

2144 Hello. 

2145 """, 

2146 } 

2147 make_files(d) 

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

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

2150 self.assert_made_files_unchanged(d) 

2151 

2152 def test_check_good(self): 

2153 d = { 

2154 "unchanged.cog": """\ 

2155 //[[[cog 

2156 cog.outl("hello world") 

2157 //]]] 

2158 hello world 

2159 //[[[end]]] 

2160 """, 

2161 } 

2162 make_files(d) 

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

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

2165 self.assert_made_files_unchanged(d) 

2166 

2167 def test_check_bad(self): 

2168 d = { 

2169 "changed.cog": """\ 

2170 //[[[cog 

2171 cog.outl("goodbye world") 

2172 //]]] 

2173 hello world 

2174 //[[[end]]] 

2175 """, 

2176 } 

2177 make_files(d) 

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

2179 self.assertEqual( 

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

2181 ) 

2182 self.assert_made_files_unchanged(d) 

2183 

2184 def test_check_bad_with_diff(self): 

2185 d = { 

2186 "skittering.cog": """\ 

2187 //[[[cog 

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

2189 cog.outl("goodbye world") 

2190 //]]] 

2191 number 0 

2192 number 1 

2193 number 2 

2194 number 3 

2195 number 4 

2196 hello world 

2197 //[[[end]]] 

2198 """, 

2199 } 

2200 make_files(d) 

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

2202 output = """\ 

2203 Checking skittering.cog (changed) 

2204 --- current skittering.cog 

2205 +++ changed skittering.cog 

2206 @@ -7,5 +7,5 @@ 

2207 number 2 

2208 number 3 

2209 number 4 

2210 -hello world 

2211 +goodbye world 

2212 //[[[end]]] 

2213 Check failed 

2214 """ 

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

2216 self.assert_made_files_unchanged(d) 

2217 

2218 def test_check_bad_with_message(self): 

2219 d = { 

2220 "changed.cog": """\ 

2221 //[[[cog 

2222 cog.outl("goodbye world") 

2223 //]]] 

2224 hello world 

2225 //[[[end]]] 

2226 """, 

2227 } 

2228 make_files(d) 

2229 self.run_check( 

2230 ["--check-fail-msg=Run `make cogged` to fix", "changed.cog"], status=5 

2231 ) 

2232 self.assertEqual( 

2233 self.output.getvalue(), 

2234 "Checking changed.cog (changed)\nCheck failed: Run `make cogged` to fix\n", 

2235 ) 

2236 self.assert_made_files_unchanged(d) 

2237 

2238 def test_check_mixed(self): 

2239 d = { 

2240 "unchanged.cog": """\ 

2241 //[[[cog 

2242 cog.outl("hello world") 

2243 //]]] 

2244 hello world 

2245 //[[[end]]] 

2246 """, 

2247 "changed.cog": """\ 

2248 //[[[cog 

2249 cog.outl("goodbye world") 

2250 //]]] 

2251 hello world 

2252 //[[[end]]] 

2253 """, 

2254 } 

2255 make_files(d) 

2256 for verbosity, output in [ 

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

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

2259 ( 

2260 "2", 

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

2262 ), 

2263 ]: 

2264 self.new_cog() 

2265 self.run_check( 

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

2267 ) 

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

2269 self.assert_made_files_unchanged(d) 

2270 

2271 def test_check_with_good_checksum(self): 

2272 d = { 

2273 "good.txt": """\ 

2274 //[[[cog 

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

2276 cog.outl("generated by cog") 

2277 cog.outl("blah blah.") 

2278 //]]] 

2279 This line was newly 

2280 generated by cog 

2281 blah blah. 

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

2283 """, 

2284 } 

2285 make_files(d) 

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

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

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

2289 self.assert_made_files_unchanged(d) 

2290 

2291 def test_check_with_bad_checksum(self): 

2292 d = { 

2293 "bad.txt": """\ 

2294 //[[[cog 

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

2296 cog.outl("generated by cog") 

2297 cog.outl("blah blah.") 

2298 //]]] 

2299 This line was newly 

2300 generated by cog 

2301 blah blah. 

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

2303 """, 

2304 } 

2305 make_files(d) 

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

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

2308 self.assertEqual( 

2309 self.output.getvalue(), 

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

2311 ) 

2312 self.assert_made_files_unchanged(d) 

2313 

2314 def test_check_with_good_sum(self): 

2315 d = { 

2316 "good.txt": """\ 

2317 //[[[cog 

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

2319 cog.outl("generated by cog") 

2320 cog.outl("blah blah.") 

2321 //]]] 

2322 This line was newly 

2323 generated by cog 

2324 blah blah. 

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

2326 """, 

2327 } 

2328 make_files(d) 

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

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

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

2332 self.assert_made_files_unchanged(d) 

2333 

2334 def test_check_with_bad_sum(self): 

2335 d = { 

2336 "bad.txt": """\ 

2337 //[[[cog 

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

2339 cog.outl("generated by cog") 

2340 cog.outl("blah blah.") 

2341 //]]] 

2342 This line was newly 

2343 generated by cog 

2344 blah blah. 

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

2346 """, 

2347 } 

2348 make_files(d) 

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

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

2351 self.assertEqual( 

2352 self.output.getvalue(), 

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

2354 ) 

2355 self.assert_made_files_unchanged(d) 

2356 

2357 

2358class WritabilityTests(TestCaseWithTempDir): 

2359 d = { 

2360 "test.cog": """\ 

2361 //[[[cog 

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

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

2364 //]]] 

2365 //[[[end]]] 

2366 """, 

2367 "test.out": """\ 

2368 //[[[cog 

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

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

2371 //]]] 

2372 void DoSomething(); 

2373 void DoAnotherThing(); 

2374 void DoLastThing(); 

2375 //[[[end]]] 

2376 """, 

2377 } 

2378 

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

2380 # for Windows 

2381 cmd_w_args = "attrib -R %s" 

2382 cmd_w_asterisk = "attrib -R *" 

2383 else: 

2384 # for unix-like 

2385 cmd_w_args = "chmod +w %s" 

2386 cmd_w_asterisk = "chmod +w *" 

2387 

2388 def setUp(self): 

2389 super().setUp() 

2390 make_files(self.d) 

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

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

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

2394 

2395 def tearDown(self): 

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

2397 super().tearDown() 

2398 

2399 def test_readonly_no_command(self): 

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

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

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

2403 

2404 def test_readonly_with_command(self): 

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

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

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

2408 

2409 def test_readonly_with_command_with_no_slot(self): 

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

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

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

2413 

2414 def test_readonly_with_ineffectual_command(self): 

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

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

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

2418 

2419 

2420class ChecksumTests(TestCaseWithTempDir): 

2421 def test_create_checksum_output(self): 

2422 d = { 

2423 "cog1.txt": """\ 

2424 //[[[cog 

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

2426 //]]] 

2427 This line was generated. 

2428 //[[[end]]] what 

2429 This line was not. 

2430 """, 

2431 "cog1.out": """\ 

2432 //[[[cog 

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

2434 //]]] 

2435 This line was generated. 

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

2437 This line was not. 

2438 """, 

2439 } 

2440 

2441 make_files(d) 

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

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

2444 

2445 def test_check_checksum_output(self): 

2446 d = { 

2447 "cog1.txt": """\ 

2448 //[[[cog 

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

2450 cog.outl("generated by cog") 

2451 cog.outl("blah blah.") 

2452 //]]] 

2453 This line was generated. 

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

2455 """, 

2456 "cog1.out": """\ 

2457 //[[[cog 

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

2459 cog.outl("generated by cog") 

2460 cog.outl("blah blah.") 

2461 //]]] 

2462 This line was newly 

2463 generated by cog 

2464 blah blah. 

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

2466 """, 

2467 } 

2468 

2469 make_files(d) 

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

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

2472 

2473 def test_check_old_checksum_format(self): 

2474 # Test that old checksum format can still be read 

2475 d = { 

2476 "cog1.txt": """\ 

2477 //[[[cog 

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

2479 cog.outl("generated by cog") 

2480 cog.outl("blah blah.") 

2481 //]]] 

2482 This line was generated. 

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

2484 """, 

2485 "cog1.out": """\ 

2486 //[[[cog 

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

2488 cog.outl("generated by cog") 

2489 cog.outl("blah blah.") 

2490 //]]] 

2491 This line was newly 

2492 generated by cog 

2493 blah blah. 

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

2495 """, 

2496 } 

2497 

2498 make_files(d) 

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

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

2501 

2502 def test_remove_checksum_output(self): 

2503 d = { 

2504 "cog1.txt": """\ 

2505 //[[[cog 

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

2507 cog.outl("generated by cog") 

2508 cog.outl("blah blah.") 

2509 //]]] 

2510 This line was generated. 

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

2512 """, 

2513 "cog1.out": """\ 

2514 //[[[cog 

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

2516 cog.outl("generated by cog") 

2517 cog.outl("blah blah.") 

2518 //]]] 

2519 This line was newly 

2520 generated by cog 

2521 blah blah. 

2522 //[[[end]]] fooey 

2523 """, 

2524 } 

2525 

2526 make_files(d) 

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

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

2529 

2530 def test_tampered_checksum_output(self): 

2531 d = { 

2532 "cog1.txt": """\ 

2533 //[[[cog 

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

2535 cog.outl("generated by cog") 

2536 cog.outl("blah blah.") 

2537 //]]] 

2538 Xhis line was newly 

2539 generated by cog 

2540 blah blah. 

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

2542 """, 

2543 "cog2.txt": """\ 

2544 //[[[cog 

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

2546 cog.outl("generated by cog") 

2547 cog.outl("blah blah.") 

2548 //]]] 

2549 This line was newly 

2550 generated by cog 

2551 blah blah! 

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

2553 """, 

2554 "cog3.txt": """\ 

2555 //[[[cog 

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

2557 cog.outl("generated by cog") 

2558 cog.outl("blah blah.") 

2559 //]]] 

2560 

2561 This line was newly 

2562 generated by cog 

2563 blah blah. 

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

2565 """, 

2566 "cog4.txt": """\ 

2567 //[[[cog 

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

2569 cog.outl("generated by cog") 

2570 cog.outl("blah blah.") 

2571 //]]] 

2572 This line was newly 

2573 generated by cog 

2574 blah blah.. 

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

2576 """, 

2577 "cog5.txt": """\ 

2578 //[[[cog 

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

2580 cog.outl("generated by cog") 

2581 cog.outl("blah blah.") 

2582 //]]] 

2583 This line was newly 

2584 generated by cog 

2585 blah blah. 

2586 extra 

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

2588 """, 

2589 "cog6.txt": """\ 

2590 //[[[cog 

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

2592 cog.outl("generated by cog") 

2593 cog.outl("blah blah.") 

2594 //]]] 

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

2596 """, 

2597 } 

2598 

2599 make_files(d) 

2600 with self.assertRaisesRegex( 

2601 CogError, 

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

2603 ): 

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

2605 with self.assertRaisesRegex( 

2606 CogError, 

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

2608 ): 

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

2610 with self.assertRaisesRegex( 

2611 CogError, 

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

2613 ): 

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

2615 with self.assertRaisesRegex( 

2616 CogError, 

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

2618 ): 

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

2620 with self.assertRaisesRegex( 

2621 CogError, 

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

2623 ): 

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

2625 with self.assertRaisesRegex( 

2626 CogError, 

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

2628 ): 

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

2630 

2631 def test_argv_isnt_modified(self): 

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

2633 orig_argv = argv[:] 

2634 self.cog.callable_main(argv) 

2635 self.assertEqual(argv, orig_argv) 

2636 

2637 

2638class CustomMarkerTests(TestCaseWithTempDir): 

2639 def test_customer_markers(self): 

2640 d = { 

2641 "test.cog": """\ 

2642 //{{ 

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

2644 //}} 

2645 //{{end}} 

2646 """, 

2647 "test.out": """\ 

2648 //{{ 

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

2650 //}} 

2651 void MyFunction(); 

2652 //{{end}} 

2653 """, 

2654 } 

2655 

2656 make_files(d) 

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

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

2659 

2660 def test_truly_wacky_markers(self): 

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

2662 d = { 

2663 "test.cog": """\ 

2664 //**( 

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

2666 //**) 

2667 //**(end)** 

2668 """, 

2669 "test.out": """\ 

2670 //**( 

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

2672 //**) 

2673 void MyFunction(); 

2674 //**(end)** 

2675 """, 

2676 } 

2677 

2678 make_files(d) 

2679 self.cog.callable_main( 

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

2681 ) 

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

2683 

2684 def test_change_just_one_marker(self): 

2685 d = { 

2686 "test.cog": """\ 

2687 //**( 

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

2689 //]]] 

2690 //[[[end]]] 

2691 """, 

2692 "test.out": """\ 

2693 //**( 

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

2695 //]]] 

2696 void MyFunction(); 

2697 //[[[end]]] 

2698 """, 

2699 } 

2700 

2701 make_files(d) 

2702 self.cog.callable_main( 

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

2704 ) 

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

2706 

2707 

2708class BlakeTests(TestCaseWithTempDir): 

2709 # Blake Winton's contributions. 

2710 def test_delete_code(self): 

2711 # -o sets the output file. 

2712 d = { 

2713 "test.cog": """\ 

2714 // This is my C++ file. 

2715 //[[[cog 

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

2717 for fn in fnames: 

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

2719 //]]] 

2720 Some Sample Code Here 

2721 //[[[end]]]Data Data 

2722 And Some More 

2723 """, 

2724 "test.out": """\ 

2725 // This is my C++ file. 

2726 void DoSomething(); 

2727 void DoAnotherThing(); 

2728 void DoLastThing(); 

2729 And Some More 

2730 """, 

2731 } 

2732 

2733 make_files(d) 

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

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

2736 

2737 def test_delete_code_with_dash_r_fails(self): 

2738 d = { 

2739 "test.cog": """\ 

2740 // This is my C++ file. 

2741 """ 

2742 } 

2743 

2744 make_files(d) 

2745 with self.assertRaisesRegex( 

2746 CogUsageError, 

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

2748 ): 

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

2750 

2751 def test_setting_globals(self): 

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

2753 # processFile(). 

2754 d = { 

2755 "test.cog": """\ 

2756 // This is my C++ file. 

2757 //[[[cog 

2758 for fn in fnames: 

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

2760 //]]] 

2761 Some Sample Code Here 

2762 //[[[end]]]""", 

2763 "test.out": """\ 

2764 // This is my C++ file. 

2765 void DoBlake(); 

2766 void DoWinton(); 

2767 void DoContribution(); 

2768 """, 

2769 } 

2770 

2771 make_files(d) 

2772 globals = {} 

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

2774 self.cog.options.delete_code = True 

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

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

2777 

2778 

2779class ErrorCallTests(TestCaseWithTempDir): 

2780 def test_error_call_has_no_traceback(self): 

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

2782 d = { 

2783 "error.cog": """\ 

2784 //[[[cog 

2785 cog.error("Something Bad!") 

2786 //]]] 

2787 //[[[end]]] 

2788 """, 

2789 } 

2790 

2791 make_files(d) 

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

2793 output = self.output.getvalue() 

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

2795 

2796 def test_real_error_has_traceback(self): 

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

2798 d = { 

2799 "error.cog": """\ 

2800 //[[[cog 

2801 raise RuntimeError("Hey!") 

2802 //]]] 

2803 //[[[end]]] 

2804 """, 

2805 } 

2806 

2807 make_files(d) 

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

2809 output = self.output.getvalue() 

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

2811 self.assertTrue( 

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

2813 ) 

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

2815 

2816 

2817class HashHandlerTests(TestCase): 

2818 """Test cases for HashHandler functionality.""" 

2819 

2820 def setUp(self): 

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

2822 

2823 def test_validate_hash_with_base64_mismatch(self): 

2824 # Test the base64 validation branch with a mismatch 

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

2826 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346" 

2827 

2828 with self.assertRaises(ValueError) as cm: 

2829 self.handler.validate_hash(line, expected_hash) 

2830 self.assertEqual( 

2831 str(cm.exception), 

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

2833 ) 

2834 

2835 def test_validate_hash_with_base64_match(self): 

2836 # Test the base64 validation branch with a match 

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

2838 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346" 

2839 

2840 # Should not raise an exception 

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

2842 self.assertTrue(result) 

2843 

2844 

2845# Things not yet tested: 

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