Coverage for cogapp / test_cogapp.py: 29.54%

911 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-09 06:46 -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 def test_unrelated_triple_closing_brackets(self): 

471 # Python code with accurate type annotations may have triple closing brackets. 

472 # The Python code may be embedded in other files with cog usage, 

473 # such as RST files or GitHub workflows. Cog must not error on this input. 

474 # The example below is a fully-valid, executable GitHub workflow. 

475 infile = """\ 

476 on: 

477 push: 

478 jobs: 

479 demo: 

480 runs-on: ubuntu-latest 

481 steps: 

482 - shell: bash 

483 run: | 

484 # [[[cog cog.outl("echo 'hello'") ]]] 

485 echo 'hello' 

486 # [[[end]]] 

487 - shell: python 

488 run: | 

489 def demo() -> tuple[tuple[tuple[str]]]: 

490 # This is what's being tested ^^^ 

491 # 

492 return ((("hello world",),),) 

493 print(demo()[0][0][0]) 

494 """ 

495 

496 infile = reindent_block(infile) 

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

498 

499 

500class CogOptionsTests(TestCase): 

501 """Test the CogOptions class.""" 

502 

503 def test_equality(self): 

504 o = CogOptions() 

505 p = CogOptions() 

506 self.assertEqual(o, p) 

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

508 self.assertNotEqual(o, p) 

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

510 self.assertEqual(o, p) 

511 

512 def test_cloning(self): 

513 o = CogOptions() 

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

515 p = o.clone() 

516 self.assertEqual(o, p) 

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

518 self.assertNotEqual(o, p) 

519 q = CogOptions() 

520 q.parse_args( 

521 [ 

522 "-I", 

523 "fooey", 

524 "-I", 

525 "booey", 

526 "-s", 

527 " /*x*/", 

528 "-I", 

529 "huey", 

530 "-D", 

531 "foo=quux", 

532 ] 

533 ) 

534 self.assertEqual(p, q) 

535 

536 def test_combining_flags(self): 

537 # Single-character flags can be combined. 

538 o = CogOptions() 

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

540 p = CogOptions() 

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

542 self.assertEqual(o, p) 

543 

544 def test_markers(self): 

545 o = Markers.from_arg("a b c") 

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

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

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

549 

550 def test_markers_switch(self): 

551 o = CogOptions() 

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

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

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

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

556 

557 

558class FileStructureTests(TestCase): 

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

560 

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

562 infile = reindent_block(infile) 

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

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

565 

566 def test_begin_no_end(self): 

567 infile = """\ 

568 Fooey 

569 #[[[cog 

570 cog.outl('hello') 

571 """ 

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

573 

574 def test_no_eoo(self): 

575 infile = """\ 

576 Fooey 

577 #[[[cog 

578 cog.outl('hello') 

579 #]]] 

580 """ 

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

582 

583 infile2 = """\ 

584 Fooey 

585 #[[[cog 

586 cog.outl('hello') 

587 #]]] 

588 #[[[cog 

589 cog.outl('goodbye') 

590 #]]] 

591 """ 

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

593 

594 def test_start_with_eoo(self): 

595 infile = """\ 

596 #[[[end]]] 

597 """ 

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

599 

600 infile2 = """\ 

601 #[[[cog 

602 cog.outl('hello') 

603 #]]] 

604 #[[[end]]] 

605 #[[[end]]] 

606 """ 

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

608 

609 def test_no_end(self): 

610 infile = """\ 

611 #[[[cog 

612 cog.outl("hello") 

613 #[[[end]]] 

614 """ 

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

616 

617 infile2 = """\ 

618 #[[[cog 

619 cog.outl('hello') 

620 #]]] 

621 #[[[end]]] 

622 #[[[cog 

623 cog.outl("hello") 

624 #[[[end]]] 

625 """ 

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

627 

628 def test_two_begins(self): 

629 infile = """\ 

630 #[[[cog 

631 #[[[cog 

632 cog.outl("hello") 

633 #]]] 

634 #[[[end]]] 

635 """ 

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

637 

638 infile2 = """\ 

639 #[[[cog 

640 cog.outl("hello") 

641 #]]] 

642 #[[[end]]] 

643 #[[[cog 

644 #[[[cog 

645 cog.outl("hello") 

646 #]]] 

647 #[[[end]]] 

648 """ 

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

650 

651 def test_two_ends(self): 

652 infile = """\ 

653 #[[[cog 

654 cog.outl("hello") 

655 #]]] 

656 #]]] 

657 #[[[end]]] 

658 """ 

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

660 

661 infile2 = """\ 

662 #[[[cog 

663 cog.outl("hello") 

664 #]]] 

665 #[[[end]]] 

666 #[[[cog 

667 cog.outl("hello") 

668 #]]] 

669 #]]] 

670 #[[[end]]] 

671 """ 

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

673 

674 

675class CogErrorTests(TestCase): 

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

677 

678 def test_error_msg(self): 

679 infile = """\ 

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

681 [[[end]]] 

682 """ 

683 

684 infile = reindent_block(infile) 

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

686 Cog().process_string(infile) 

687 

688 def test_error_no_msg(self): 

689 infile = """\ 

690 [[[cog cog.error()]]] 

691 [[[end]]] 

692 """ 

693 

694 infile = reindent_block(infile) 

695 with self.assertRaisesRegex( 

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

697 ): 

698 Cog().process_string(infile) 

699 

700 def test_no_error_if_error_not_called(self): 

701 infile = """\ 

702 --[[[cog 

703 --import cog 

704 --for i in range(3): 

705 -- if i > 10: 

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

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

708 --]]] 

709 xx0 

710 xx1 

711 xx2 

712 --[[[end]]] 

713 """ 

714 

715 infile = reindent_block(infile) 

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

717 

718 

719class CogGeneratorGetCodeTests(TestCase): 

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

721 

722 def setUp(self): 

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

724 # the functions we're going to use. 

725 self.gen = CogGenerator() 

726 self.m = self.gen.parse_marker 

727 self.parse_line = self.gen.parse_line 

728 

729 def test_empty(self): 

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

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

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

733 

734 def test_simple(self): 

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

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

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

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

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

740 

741 def test_compressed1(self): 

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

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

744 self.parse_line("// hello") 

745 self.parse_line("// bye") 

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

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

748 

749 def test_compressed2(self): 

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

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

752 self.parse_line("hello") 

753 self.parse_line("bye") 

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

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

756 

757 def test_compressed3(self): 

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

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

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

761 self.parse_line("bye") 

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

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

764 

765 def test_compressed4(self): 

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

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

768 self.parse_line("hello") 

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

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

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

772 

773 def test_no_common_prefix_for_markers(self): 

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

775 # C++ compiler. 

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

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

778 self.parse_line("") 

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

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

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

782 

783 

784class TestCaseWithTempDir(TestCase): 

785 def new_cog(self): 

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

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

788 self.cog = Cog() 

789 self.output = io.StringIO() 

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

791 

792 def setUp(self): 

793 # Create a temporary directory. 

794 self.tempdir = os.path.join( 

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

796 ) 

797 os.mkdir(self.tempdir) 

798 self.olddir = os.getcwd() 

799 os.chdir(self.tempdir) 

800 self.new_cog() 

801 

802 def tearDown(self): 

803 os.chdir(self.olddir) 

804 # Get rid of the temporary directory. 

805 shutil.rmtree(self.tempdir) 

806 

807 def assertFilesSame(self, file_name1, file_name2): 

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

809 text1 = f1.read() 

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

811 text2 = f2.read() 

812 self.assertEqual(text1, text2) 

813 

814 def assertFileContent(self, fname, content): 

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

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

817 file_content = f.read() 

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

819 

820 

821class ArgumentHandlingTests(TestCaseWithTempDir): 

822 def test_argument_failure(self): 

823 # Return value 2 means usage problem. 

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

825 output = self.output.getvalue() 

826 self.assertIn("unrecognized arguments: -j", output) 

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

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

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

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

831 

832 def test_no_dash_o_and_at_file(self): 

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

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

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

836 

837 def test_no_dash_o_and_amp_file(self): 

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

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

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

841 

842 def test_no_diff_without_check(self): 

843 with self.assertRaisesRegex( 

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

845 ): 

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

847 

848 def test_dash_v(self): 

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

850 output = self.output.getvalue() 

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

852 

853 def produces_help(self, args): 

854 self.new_cog() 

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

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

857 output = self.output.getvalue() 

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

859 

860 def test_dash_h(self): 

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

862 self.produces_help("-h") 

863 self.produces_help("--help") 

864 self.produces_help("-?") 

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

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

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

868 

869 def test_dash_o_and_dash_r(self): 

870 d = { 

871 "cogfile.txt": """\ 

872 # Please run cog 

873 """ 

874 } 

875 

876 make_files(d) 

877 with self.assertRaisesRegex( 

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

879 ): 

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

881 

882 def test_dash_z(self): 

883 d = { 

884 "test.cog": """\ 

885 // This is my C++ file. 

886 //[[[cog 

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

888 for fn in fnames: 

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

890 //]]] 

891 """, 

892 "test.out": """\ 

893 // This is my C++ file. 

894 //[[[cog 

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

896 for fn in fnames: 

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

898 //]]] 

899 void DoSomething(); 

900 void DoAnotherThing(); 

901 void DoLastThing(); 

902 """, 

903 } 

904 

905 make_files(d) 

906 with self.assertRaisesRegex( 

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

908 ): 

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

910 self.new_cog() 

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

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

913 

914 def test_bad_dash_d(self): 

915 with self.assertRaisesRegex( 

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

917 ): 

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

919 with self.assertRaisesRegex( 

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

921 ): 

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

923 

924 def test_bad_markers(self): 

925 with self.assertRaisesRegex( 

926 CogUsageError, 

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

928 ): 

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

930 with self.assertRaisesRegex( 

931 CogUsageError, 

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

933 ): 

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

935 

936 

937class TestMain(TestCaseWithTempDir): 

938 def setUp(self): 

939 super().setUp() 

940 self.old_argv = sys.argv[:] 

941 self.old_stderr = sys.stderr 

942 sys.stderr = io.StringIO() 

943 

944 def tearDown(self): 

945 sys.stderr = self.old_stderr 

946 sys.argv = self.old_argv 

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

948 super().tearDown() 

949 

950 def test_main_function(self): 

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

952 ret = main() 

953 self.assertEqual(ret, 2) 

954 stderr = sys.stderr.getvalue() 

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

956 

957 files = { 

958 "test.cog": """\ 

959 //[[[cog 

960 def func(): 

961 import mycode 

962 mycode.boom() 

963 //]]] 

964 //[[[end]]] 

965 ----- 

966 //[[[cog 

967 func() 

968 //]]] 

969 //[[[end]]] 

970 """, 

971 "mycode.py": """\ 

972 def boom(): 

973 [][0] 

974 """, 

975 } 

976 

977 def test_error_report(self): 

978 self.check_error_report() 

979 

980 def test_error_report_with_prologue(self): 

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

982 

983 def check_error_report(self, *args): 

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

985 make_files(self.files) 

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

987 main() 

988 expected = reindent_block("""\ 

989 Traceback (most recent call last): 

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

991 func() 

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

993 mycode.boom() 

994 File "MYCODE", line 2, in boom 

995 [][0] 

996 IndexError: list index out of range 

997 """) 

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

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

1000 

1001 def test_error_in_prologue(self): 

1002 make_files(self.files) 

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

1004 main() 

1005 expected = reindent_block("""\ 

1006 Traceback (most recent call last): 

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

1008 import mycode; mycode.boom() 

1009 File "MYCODE", line 2, in boom 

1010 [][0] 

1011 IndexError: list index out of range 

1012 """) 

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

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

1015 

1016 

1017class TestFileHandling(TestCaseWithTempDir): 

1018 def test_simple(self): 

1019 d = { 

1020 "test.cog": """\ 

1021 // This is my C++ file. 

1022 //[[[cog 

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

1024 for fn in fnames: 

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

1026 //]]] 

1027 //[[[end]]] 

1028 """, 

1029 "test.out": """\ 

1030 // This is my C++ file. 

1031 //[[[cog 

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

1033 for fn in fnames: 

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

1035 //]]] 

1036 void DoSomething(); 

1037 void DoAnotherThing(); 

1038 void DoLastThing(); 

1039 //[[[end]]] 

1040 """, 

1041 } 

1042 

1043 make_files(d) 

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

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

1046 output = self.output.getvalue() 

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

1048 

1049 def test_print_output(self): 

1050 d = { 

1051 "test.cog": """\ 

1052 // This is my C++ file. 

1053 //[[[cog 

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

1055 for fn in fnames: 

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

1057 //]]] 

1058 //[[[end]]] 

1059 """, 

1060 "test.out": """\ 

1061 // This is my C++ file. 

1062 //[[[cog 

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

1064 for fn in fnames: 

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

1066 //]]] 

1067 void DoSomething(); 

1068 void DoAnotherThing(); 

1069 void DoLastThing(); 

1070 //[[[end]]] 

1071 """, 

1072 } 

1073 

1074 make_files(d) 

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

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

1077 output = self.output.getvalue() 

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

1079 

1080 def test_wildcards(self): 

1081 d = { 

1082 "test.cog": """\ 

1083 // This is my C++ file. 

1084 //[[[cog 

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

1086 for fn in fnames: 

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

1088 //]]] 

1089 //[[[end]]] 

1090 """, 

1091 "test2.cog": """\ 

1092 // This is my C++ file. 

1093 //[[[cog 

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

1095 for fn in fnames: 

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

1097 //]]] 

1098 //[[[end]]] 

1099 """, 

1100 "test.out": """\ 

1101 // This is my C++ file. 

1102 //[[[cog 

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

1104 for fn in fnames: 

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

1106 //]]] 

1107 void DoSomething(); 

1108 void DoAnotherThing(); 

1109 void DoLastThing(); 

1110 //[[[end]]] 

1111 """, 

1112 "not_this_one.cog": """\ 

1113 // This is my C++ file. 

1114 //[[[cog 

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

1116 for fn in fnames: 

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

1118 //]]] 

1119 //[[[end]]] 

1120 """, 

1121 "not_this_one.out": """\ 

1122 // This is my C++ file. 

1123 //[[[cog 

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

1125 for fn in fnames: 

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

1127 //]]] 

1128 //[[[end]]] 

1129 """, 

1130 } 

1131 

1132 make_files(d) 

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

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

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

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

1137 output = self.output.getvalue() 

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

1139 

1140 def test_output_file(self): 

1141 # -o sets the output file. 

1142 d = { 

1143 "test.cog": """\ 

1144 // This is my C++ file. 

1145 //[[[cog 

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

1147 for fn in fnames: 

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

1149 //]]] 

1150 //[[[end]]] 

1151 """, 

1152 "test.out": """\ 

1153 // This is my C++ file. 

1154 //[[[cog 

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

1156 for fn in fnames: 

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

1158 //]]] 

1159 void DoSomething(); 

1160 void DoAnotherThing(); 

1161 void DoLastThing(); 

1162 //[[[end]]] 

1163 """, 

1164 } 

1165 

1166 make_files(d) 

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

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

1169 

1170 def test_at_file(self): 

1171 d = { 

1172 "one.cog": """\ 

1173 //[[[cog 

1174 cog.outl("hello world") 

1175 //]]] 

1176 //[[[end]]] 

1177 """, 

1178 "one.out": """\ 

1179 //[[[cog 

1180 cog.outl("hello world") 

1181 //]]] 

1182 hello world 

1183 //[[[end]]] 

1184 """, 

1185 "two.cog": """\ 

1186 //[[[cog 

1187 cog.outl("goodbye cruel world") 

1188 //]]] 

1189 //[[[end]]] 

1190 """, 

1191 "two.out": """\ 

1192 //[[[cog 

1193 cog.outl("goodbye cruel world") 

1194 //]]] 

1195 goodbye cruel world 

1196 //[[[end]]] 

1197 """, 

1198 "cogfiles.txt": """\ 

1199 # Please run cog 

1200 one.cog 

1201 

1202 two.cog 

1203 """, 

1204 } 

1205 

1206 make_files(d) 

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

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

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

1210 output = self.output.getvalue() 

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

1212 

1213 def test_nested_at_file(self): 

1214 d = { 

1215 "one.cog": """\ 

1216 //[[[cog 

1217 cog.outl("hello world") 

1218 //]]] 

1219 //[[[end]]] 

1220 """, 

1221 "one.out": """\ 

1222 //[[[cog 

1223 cog.outl("hello world") 

1224 //]]] 

1225 hello world 

1226 //[[[end]]] 

1227 """, 

1228 "two.cog": """\ 

1229 //[[[cog 

1230 cog.outl("goodbye cruel world") 

1231 //]]] 

1232 //[[[end]]] 

1233 """, 

1234 "two.out": """\ 

1235 //[[[cog 

1236 cog.outl("goodbye cruel world") 

1237 //]]] 

1238 goodbye cruel world 

1239 //[[[end]]] 

1240 """, 

1241 "cogfiles.txt": """\ 

1242 # Please run cog 

1243 one.cog 

1244 @cogfiles2.txt 

1245 """, 

1246 "cogfiles2.txt": """\ 

1247 # This one too, please. 

1248 two.cog 

1249 """, 

1250 } 

1251 

1252 make_files(d) 

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

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

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

1256 output = self.output.getvalue() 

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

1258 

1259 def test_at_file_with_args(self): 

1260 d = { 

1261 "both.cog": """\ 

1262 //[[[cog 

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

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

1265 //]]] 

1266 //[[[end]]] 

1267 """, 

1268 "one.out": """\ 

1269 //[[[cog 

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

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

1272 //]]] 

1273 one: True // ONE 

1274 two: False // ONE 

1275 //[[[end]]] 

1276 """, 

1277 "two.out": """\ 

1278 //[[[cog 

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

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

1281 //]]] 

1282 one: False // TWO 

1283 two: True // TWO 

1284 //[[[end]]] 

1285 """, 

1286 "cogfiles.txt": """\ 

1287 # Please run cog 

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

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

1290 """, 

1291 } 

1292 

1293 make_files(d) 

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

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

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

1297 

1298 def test_at_file_with_bad_arg_combo(self): 

1299 d = { 

1300 "both.cog": """\ 

1301 //[[[cog 

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

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

1304 //]]] 

1305 //[[[end]]] 

1306 """, 

1307 "cogfiles.txt": """\ 

1308 # Please run cog 

1309 both.cog 

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

1311 """, 

1312 } 

1313 

1314 make_files(d) 

1315 with self.assertRaisesRegex( 

1316 CogUsageError, 

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

1318 ): 

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

1320 

1321 def test_at_file_with_tricky_filenames(self): 

1322 def fix_backslashes(files_txt): 

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

1324 if sys.platform != "win32": 

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

1326 return files_txt 

1327 

1328 d = { 

1329 "one 1.cog": """\ 

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

1331 """, 

1332 "one.out": """\ 

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

1334 hello world //xxx 

1335 """, 

1336 "subdir": { 

1337 "subback.cog": """\ 

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

1339 """, 

1340 "subfwd.cog": """\ 

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

1342 """, 

1343 }, 

1344 "subback.out": """\ 

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

1346 down deep with backslashes //yyy 

1347 """, 

1348 "subfwd.out": """\ 

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

1350 down deep with slashes //zzz 

1351 """, 

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

1353 # Please run cog 

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

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

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

1357 """), 

1358 } 

1359 

1360 make_files(d) 

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

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

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

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

1365 

1366 def test_amp_file(self): 

1367 d = { 

1368 "code": { 

1369 "files_to_cog": """\ 

1370 # A locally resolved file name. 

1371 test.cog 

1372 """, 

1373 "test.cog": """\ 

1374 //[[[cog 

1375 import myampsubmodule 

1376 //]]] 

1377 //[[[end]]] 

1378 """, 

1379 "test.out": """\ 

1380 //[[[cog 

1381 import myampsubmodule 

1382 //]]] 

1383 Hello from myampsubmodule 

1384 //[[[end]]] 

1385 """, 

1386 "myampsubmodule.py": """\ 

1387 import cog 

1388 cog.outl("Hello from myampsubmodule") 

1389 """, 

1390 } 

1391 } 

1392 

1393 make_files(d) 

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

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

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

1397 

1398 def run_with_verbosity(self, verbosity): 

1399 d = { 

1400 "unchanged.cog": """\ 

1401 //[[[cog 

1402 cog.outl("hello world") 

1403 //]]] 

1404 hello world 

1405 //[[[end]]] 

1406 """, 

1407 "changed.cog": """\ 

1408 //[[[cog 

1409 cog.outl("goodbye cruel world") 

1410 //]]] 

1411 //[[[end]]] 

1412 """, 

1413 "cogfiles.txt": """\ 

1414 unchanged.cog 

1415 changed.cog 

1416 """, 

1417 } 

1418 

1419 make_files(d) 

1420 self.cog.callable_main( 

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

1422 ) 

1423 output = self.output.getvalue() 

1424 return output 

1425 

1426 def test_verbosity0(self): 

1427 output = self.run_with_verbosity("0") 

1428 self.assertEqual(output, "") 

1429 

1430 def test_verbosity1(self): 

1431 output = self.run_with_verbosity("1") 

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

1433 

1434 def test_verbosity2(self): 

1435 output = self.run_with_verbosity("2") 

1436 self.assertEqual( 

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

1438 ) 

1439 

1440 def test_change_dir(self): 

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

1442 d = { 

1443 "sub": { 

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

1445 }, 

1446 "test.cog": """\ 

1447 //[[[cog 

1448 import os 

1449 os.chdir("sub") 

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

1451 //]]] 

1452 //[[[end]]] 

1453 """, 

1454 "test.out": """\ 

1455 //[[[cog 

1456 import os 

1457 os.chdir("sub") 

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

1459 //]]] 

1460 Hello! 

1461 //[[[end]]] 

1462 """, 

1463 } 

1464 

1465 make_files(d) 

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

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

1468 output = self.output.getvalue() 

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

1470 

1471 

1472class CogTestLineEndings(TestCaseWithTempDir): 

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

1474 

1475 lines_in = [ 

1476 "Some text.", 

1477 "//[[[cog", 

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

1479 "//]]]", 

1480 "gobbledegook.", 

1481 "//[[[end]]]", 

1482 "epilogue.", 

1483 "", 

1484 ] 

1485 

1486 lines_out = [ 

1487 "Some text.", 

1488 "//[[[cog", 

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

1490 "//]]]", 

1491 "Cog text", 

1492 "//[[[end]]]", 

1493 "epilogue.", 

1494 "", 

1495 ] 

1496 

1497 def test_output_native_eol(self): 

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

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

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

1501 

1502 def test_output_lf_eol(self): 

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

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

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

1506 

1507 def test_replace_native_eol(self): 

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

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

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

1511 

1512 def test_replace_lf_eol(self): 

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

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

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

1516 

1517 

1518class CogTestCharacterEncoding(TestCaseWithTempDir): 

1519 def test_simple(self): 

1520 d = { 

1521 "test.cog": b"""\ 

1522 // This is my C++ file. 

1523 //[[[cog 

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

1525 //]]] 

1526 //[[[end]]] 

1527 """, 

1528 "test.out": b"""\ 

1529 // This is my C++ file. 

1530 //[[[cog 

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

1532 //]]] 

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

1534 //[[[end]]] 

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

1536 } 

1537 

1538 make_files(d) 

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

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

1541 output = self.output.getvalue() 

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

1543 

1544 def test_file_encoding_option(self): 

1545 d = { 

1546 "test.cog": b"""\ 

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

1548 //[[[cog 

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

1550 //]]] 

1551 //[[[end]]] 

1552 """, 

1553 "test.out": b"""\ 

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

1555 //[[[cog 

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

1557 //]]] 

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

1559 //[[[end]]] 

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

1561 } 

1562 

1563 make_files(d) 

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

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

1566 output = self.output.getvalue() 

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

1568 

1569 

1570class TestCaseWithImports(TestCaseWithTempDir): 

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

1572 

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

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

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

1576 

1577 """ 

1578 

1579 def setUp(self): 

1580 super().setUp() 

1581 self.sysmodulekeys = list(sys.modules) 

1582 

1583 def tearDown(self): 

1584 modstoscrub = [ 

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

1586 ] 

1587 for modname in modstoscrub: 

1588 del sys.modules[modname] 

1589 super().tearDown() 

1590 

1591 

1592class CogIncludeTests(TestCaseWithImports): 

1593 dincludes = { 

1594 "test.cog": """\ 

1595 //[[[cog 

1596 import mymodule 

1597 //]]] 

1598 //[[[end]]] 

1599 """, 

1600 "test.out": """\ 

1601 //[[[cog 

1602 import mymodule 

1603 //]]] 

1604 Hello from mymodule 

1605 //[[[end]]] 

1606 """, 

1607 "test2.out": """\ 

1608 //[[[cog 

1609 import mymodule 

1610 //]]] 

1611 Hello from mymodule in inc2 

1612 //[[[end]]] 

1613 """, 

1614 "include": { 

1615 "mymodule.py": """\ 

1616 import cog 

1617 cog.outl("Hello from mymodule") 

1618 """ 

1619 }, 

1620 "inc2": { 

1621 "mymodule.py": """\ 

1622 import cog 

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

1624 """ 

1625 }, 

1626 "inc3": { 

1627 "someothermodule.py": """\ 

1628 import cog 

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

1630 """ 

1631 }, 

1632 } 

1633 

1634 def test_need_include_path(self): 

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

1636 make_files(self.dincludes) 

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

1638 with self.assertRaisesRegex(CogUserException, msg): 

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

1640 

1641 def test_include_path(self): 

1642 # Test that -I adds include directories properly. 

1643 make_files(self.dincludes) 

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

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

1646 

1647 def test_two_include_paths(self): 

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

1649 make_files(self.dincludes) 

1650 self.cog.callable_main( 

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

1652 ) 

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

1654 

1655 def test_two_include_paths2(self): 

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

1657 make_files(self.dincludes) 

1658 self.cog.callable_main( 

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

1660 ) 

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

1662 

1663 def test_useless_include_path(self): 

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

1665 make_files(self.dincludes) 

1666 self.cog.callable_main( 

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

1668 ) 

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

1670 

1671 def test_sys_path_is_unchanged(self): 

1672 d = { 

1673 "bad.cog": """\ 

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

1675 //[[[end]]] 

1676 """, 

1677 "good.cog": """\ 

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

1679 //[[[end]]] 

1680 """, 

1681 } 

1682 

1683 make_files(d) 

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

1685 oldsyspath = sys.path[:] 

1686 self.new_cog() 

1687 self.assertEqual(oldsyspath, sys.path) 

1688 # Is it unchanged for a successful run? 

1689 self.new_cog() 

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

1691 self.assertEqual(oldsyspath, sys.path) 

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

1693 self.new_cog() 

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

1695 self.assertEqual(oldsyspath, sys.path) 

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

1697 self.new_cog() 

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

1699 self.assertEqual(oldsyspath, sys.path) 

1700 # Is it unchanged for a failed run? 

1701 self.new_cog() 

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

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

1704 self.assertEqual(oldsyspath, sys.path) 

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

1706 self.new_cog() 

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

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

1709 self.assertEqual(oldsyspath, sys.path) 

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

1711 self.new_cog() 

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

1713 self.cog.callable_main( 

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

1715 ) 

1716 self.assertEqual(oldsyspath, sys.path) 

1717 

1718 def test_sub_directories(self): 

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

1720 

1721 d = { 

1722 "code": { 

1723 "test.cog": """\ 

1724 //[[[cog 

1725 import mysubmodule 

1726 //]]] 

1727 //[[[end]]] 

1728 """, 

1729 "test.out": """\ 

1730 //[[[cog 

1731 import mysubmodule 

1732 //]]] 

1733 Hello from mysubmodule 

1734 //[[[end]]] 

1735 """, 

1736 "mysubmodule.py": """\ 

1737 import cog 

1738 cog.outl("Hello from mysubmodule") 

1739 """, 

1740 } 

1741 } 

1742 

1743 make_files(d) 

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

1745 # auto-include the current directory 

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

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

1748 

1749 

1750class CogTestsInFiles(TestCaseWithTempDir): 

1751 def test_warn_if_no_cog_code(self): 

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

1753 d = { 

1754 "with.cog": """\ 

1755 //[[[cog 

1756 cog.outl("hello world") 

1757 //]]] 

1758 hello world 

1759 //[[[end]]] 

1760 """, 

1761 "without.cog": """\ 

1762 There's no cog 

1763 code in this file. 

1764 """, 

1765 } 

1766 

1767 make_files(d) 

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

1769 output = self.output.getvalue() 

1770 self.assertNotIn("Warning", output) 

1771 self.new_cog() 

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

1773 output = self.output.getvalue() 

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

1775 self.new_cog() 

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

1777 output = self.output.getvalue() 

1778 self.assertNotIn("Warning", output) 

1779 

1780 def test_file_name_props(self): 

1781 d = { 

1782 "cog1.txt": """\ 

1783 //[[[cog 

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

1785 //]]] 

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

1787 [[[end]]] 

1788 """, 

1789 "cog1.out": """\ 

1790 //[[[cog 

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

1792 //]]] 

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

1794 [[[end]]] 

1795 """, 

1796 "cog1out.out": """\ 

1797 //[[[cog 

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

1799 //]]] 

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

1801 [[[end]]] 

1802 """, 

1803 } 

1804 

1805 make_files(d) 

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

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

1808 self.new_cog() 

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

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

1811 

1812 def test_globals_dont_cross_files(self): 

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

1814 d = { 

1815 "one.cog": """\ 

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

1817 //[[[end]]] 

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

1819 //[[[end]]] 

1820 """, 

1821 "one.out": """\ 

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

1823 //[[[end]]] 

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

1825 This was set in one.cog 

1826 //[[[end]]] 

1827 """, 

1828 "two.cog": """\ 

1829 //[[[cog 

1830 try: 

1831 cog.outl(s) 

1832 except NameError: 

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

1834 //]]] 

1835 //[[[end]]] 

1836 """, 

1837 "two.out": """\ 

1838 //[[[cog 

1839 try: 

1840 cog.outl(s) 

1841 except NameError: 

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

1843 //]]] 

1844 s isn't set! 

1845 //[[[end]]] 

1846 """, 

1847 "cogfiles.txt": """\ 

1848 # Please run cog 

1849 one.cog 

1850 

1851 two.cog 

1852 """, 

1853 } 

1854 

1855 make_files(d) 

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

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

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

1859 output = self.output.getvalue() 

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

1861 

1862 def test_remove_generated_output(self): 

1863 d = { 

1864 "cog1.txt": """\ 

1865 //[[[cog 

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

1867 //]]] 

1868 This line was generated. 

1869 //[[[end]]] 

1870 This line was not. 

1871 """, 

1872 "cog1.out": """\ 

1873 //[[[cog 

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

1875 //]]] 

1876 //[[[end]]] 

1877 This line was not. 

1878 """, 

1879 "cog1.out2": """\ 

1880 //[[[cog 

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

1882 //]]] 

1883 This line was generated. 

1884 //[[[end]]] 

1885 This line was not. 

1886 """, 

1887 } 

1888 

1889 make_files(d) 

1890 # Remove generated output. 

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

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

1893 self.new_cog() 

1894 # Regenerate the generated output. 

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

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

1897 self.new_cog() 

1898 # Remove the generated output again. 

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

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

1901 

1902 def test_msg_call(self): 

1903 infile = """\ 

1904 #[[[cog 

1905 cog.msg("Hello there!") 

1906 #]]] 

1907 #[[[end]]] 

1908 """ 

1909 infile = reindent_block(infile) 

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

1911 output = self.output.getvalue() 

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

1913 

1914 def test_error_message_has_no_traceback(self): 

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

1916 

1917 d = { 

1918 "cog1.txt": """\ 

1919 //[[[cog 

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

1921 cog.outl("generated by cog") 

1922 cog.outl("blah blah.") 

1923 //]]] 

1924 Xhis line was newly 

1925 generated by cog 

1926 blah blah. 

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

1928 """, 

1929 } 

1930 

1931 make_files(d) 

1932 stderr = io.StringIO() 

1933 self.cog.set_output(stderr=stderr) 

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

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

1936 self.assertEqual( 

1937 stderr.getvalue(), 

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

1939 ) 

1940 

1941 def test_dash_d(self): 

1942 d = { 

1943 "test.cog": """\ 

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

1945 --[[[end]]] 

1946 """, 

1947 "test.kablooey": """\ 

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

1949 Defined fooey as kablooey 

1950 --[[[end]]] 

1951 """, 

1952 "test.einstein": """\ 

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

1954 Defined fooey as e=mc2 

1955 --[[[end]]] 

1956 """, 

1957 } 

1958 

1959 make_files(d) 

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

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

1962 make_files(d) 

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

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

1965 make_files(d) 

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

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

1968 make_files(d) 

1969 self.cog.callable_main( 

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

1971 ) 

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

1973 make_files(d) 

1974 self.cog.callable_main( 

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

1976 ) 

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

1978 make_files(d) 

1979 self.cog.callable_main( 

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

1981 ) 

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

1983 

1984 def test_output_to_stdout(self): 

1985 d = { 

1986 "test.cog": """\ 

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

1988 --[[[end]]] 

1989 """ 

1990 } 

1991 

1992 make_files(d) 

1993 stderr = io.StringIO() 

1994 self.cog.set_output(stderr=stderr) 

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

1996 output = self.output.getvalue() 

1997 outerr = stderr.getvalue() 

1998 self.assertEqual( 

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

2000 ) 

2001 self.assertEqual(outerr, "") 

2002 

2003 def test_read_from_stdin(self): 

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

2005 

2006 def restore_stdin(old_stdin): 

2007 sys.stdin = old_stdin 

2008 

2009 self.addCleanup(restore_stdin, sys.stdin) 

2010 sys.stdin = stdin 

2011 

2012 stderr = io.StringIO() 

2013 self.cog.set_output(stderr=stderr) 

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

2015 output = self.output.getvalue() 

2016 outerr = stderr.getvalue() 

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

2018 self.assertEqual(outerr, "") 

2019 

2020 def test_suffix_output_lines(self): 

2021 d = { 

2022 "test.cog": """\ 

2023 Hey there. 

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

2025 ;[[[end]]] 

2026 Good bye. 

2027 """, 

2028 "test.out": """\ 

2029 Hey there. 

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

2031 a (foo) 

2032 b (foo) 

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

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

2035 """ 

2036 c (foo) 

2037 ;[[[end]]] 

2038 Good bye. 

2039 """, 

2040 } 

2041 

2042 make_files(d) 

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

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

2045 

2046 def test_empty_suffix(self): 

2047 d = { 

2048 "test.cog": """\ 

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

2050 ;[[[end]]] 

2051 """, 

2052 "test.out": """\ 

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

2054 a 

2055 b 

2056 c 

2057 ;[[[end]]] 

2058 """, 

2059 } 

2060 

2061 make_files(d) 

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

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

2064 

2065 def test_hellish_suffix(self): 

2066 d = { 

2067 "test.cog": """\ 

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

2069 """, 

2070 "test.out": """\ 

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

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

2073 

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

2075 """, 

2076 } 

2077 

2078 make_files(d) 

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

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

2081 

2082 def test_prologue(self): 

2083 d = { 

2084 "test.cog": """\ 

2085 Some text. 

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

2087 //[[[end]]] 

2088 epilogue. 

2089 """, 

2090 "test.out": """\ 

2091 Some text. 

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

2093 1.4142135623 

2094 //[[[end]]] 

2095 epilogue. 

2096 """, 

2097 } 

2098 

2099 make_files(d) 

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

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

2102 

2103 def test_threads(self): 

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

2105 # different threads. 

2106 numthreads = 20 

2107 

2108 d = {} 

2109 for i in range(numthreads): 

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

2111 "x\n" * i 

2112 + "[[[cog\n" 

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

2114 + "]]]\n" 

2115 + "[[[end]]]\n" 

2116 ) 

2117 make_files(d) 

2118 

2119 results = [] 

2120 

2121 def thread_main(num): 

2122 try: 

2123 ret = Cog().main( 

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

2125 ) 

2126 assert ret == 0 

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

2128 results.append(exc) 

2129 else: 

2130 results.append(None) 

2131 

2132 ts = [ 

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

2134 ] 

2135 for t in ts: 

2136 t.start() 

2137 for t in ts: 

2138 t.join() 

2139 assert results == [None] * numthreads 

2140 

2141 

2142class CheckTests(TestCaseWithTempDir): 

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

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

2145 print(self.output.getvalue()) 

2146 self.assertEqual(status, actual_status) 

2147 

2148 def assert_made_files_unchanged(self, d): 

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

2150 content = reindent_block(content) 

2151 if os.name == "nt": 

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

2153 self.assertFileContent(name, content) 

2154 

2155 def test_check_no_cog(self): 

2156 d = { 

2157 "hello.txt": """\ 

2158 Hello. 

2159 """, 

2160 } 

2161 make_files(d) 

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

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

2164 self.assert_made_files_unchanged(d) 

2165 

2166 def test_check_good(self): 

2167 d = { 

2168 "unchanged.cog": """\ 

2169 //[[[cog 

2170 cog.outl("hello world") 

2171 //]]] 

2172 hello world 

2173 //[[[end]]] 

2174 """, 

2175 } 

2176 make_files(d) 

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

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

2179 self.assert_made_files_unchanged(d) 

2180 

2181 def test_check_bad(self): 

2182 d = { 

2183 "changed.cog": """\ 

2184 //[[[cog 

2185 cog.outl("goodbye world") 

2186 //]]] 

2187 hello world 

2188 //[[[end]]] 

2189 """, 

2190 } 

2191 make_files(d) 

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

2193 self.assertEqual( 

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

2195 ) 

2196 self.assert_made_files_unchanged(d) 

2197 

2198 def test_check_bad_with_diff(self): 

2199 d = { 

2200 "skittering.cog": """\ 

2201 //[[[cog 

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

2203 cog.outl("goodbye world") 

2204 //]]] 

2205 number 0 

2206 number 1 

2207 number 2 

2208 number 3 

2209 number 4 

2210 hello world 

2211 //[[[end]]] 

2212 """, 

2213 } 

2214 make_files(d) 

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

2216 output = """\ 

2217 Checking skittering.cog (changed) 

2218 --- current skittering.cog 

2219 +++ changed skittering.cog 

2220 @@ -7,5 +7,5 @@ 

2221 number 2 

2222 number 3 

2223 number 4 

2224 -hello world 

2225 +goodbye world 

2226 //[[[end]]] 

2227 Check failed 

2228 """ 

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

2230 self.assert_made_files_unchanged(d) 

2231 

2232 def test_check_bad_with_message(self): 

2233 d = { 

2234 "changed.cog": """\ 

2235 //[[[cog 

2236 cog.outl("goodbye world") 

2237 //]]] 

2238 hello world 

2239 //[[[end]]] 

2240 """, 

2241 } 

2242 make_files(d) 

2243 self.run_check( 

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

2245 ) 

2246 self.assertEqual( 

2247 self.output.getvalue(), 

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

2249 ) 

2250 self.assert_made_files_unchanged(d) 

2251 

2252 def test_check_mixed(self): 

2253 d = { 

2254 "unchanged.cog": """\ 

2255 //[[[cog 

2256 cog.outl("hello world") 

2257 //]]] 

2258 hello world 

2259 //[[[end]]] 

2260 """, 

2261 "changed.cog": """\ 

2262 //[[[cog 

2263 cog.outl("goodbye world") 

2264 //]]] 

2265 hello world 

2266 //[[[end]]] 

2267 """, 

2268 } 

2269 make_files(d) 

2270 for verbosity, output in [ 

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

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

2273 ( 

2274 "2", 

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

2276 ), 

2277 ]: 

2278 self.new_cog() 

2279 self.run_check( 

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

2281 ) 

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

2283 self.assert_made_files_unchanged(d) 

2284 

2285 def test_check_with_good_checksum(self): 

2286 d = { 

2287 "good.txt": """\ 

2288 //[[[cog 

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

2290 cog.outl("generated by cog") 

2291 cog.outl("blah blah.") 

2292 //]]] 

2293 This line was newly 

2294 generated by cog 

2295 blah blah. 

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

2297 """, 

2298 } 

2299 make_files(d) 

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

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

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

2303 self.assert_made_files_unchanged(d) 

2304 

2305 def test_check_with_bad_checksum(self): 

2306 d = { 

2307 "bad.txt": """\ 

2308 //[[[cog 

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

2310 cog.outl("generated by cog") 

2311 cog.outl("blah blah.") 

2312 //]]] 

2313 This line was newly 

2314 generated by cog 

2315 blah blah. 

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

2317 """, 

2318 } 

2319 make_files(d) 

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

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

2322 self.assertEqual( 

2323 self.output.getvalue(), 

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

2325 ) 

2326 self.assert_made_files_unchanged(d) 

2327 

2328 def test_check_with_good_sum(self): 

2329 d = { 

2330 "good.txt": """\ 

2331 //[[[cog 

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

2333 cog.outl("generated by cog") 

2334 cog.outl("blah blah.") 

2335 //]]] 

2336 This line was newly 

2337 generated by cog 

2338 blah blah. 

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

2340 """, 

2341 } 

2342 make_files(d) 

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

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

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

2346 self.assert_made_files_unchanged(d) 

2347 

2348 def test_check_with_bad_sum(self): 

2349 d = { 

2350 "bad.txt": """\ 

2351 //[[[cog 

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

2353 cog.outl("generated by cog") 

2354 cog.outl("blah blah.") 

2355 //]]] 

2356 This line was newly 

2357 generated by cog 

2358 blah blah. 

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

2360 """, 

2361 } 

2362 make_files(d) 

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

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

2365 self.assertEqual( 

2366 self.output.getvalue(), 

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

2368 ) 

2369 self.assert_made_files_unchanged(d) 

2370 

2371 

2372class WritabilityTests(TestCaseWithTempDir): 

2373 d = { 

2374 "test.cog": """\ 

2375 //[[[cog 

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

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

2378 //]]] 

2379 //[[[end]]] 

2380 """, 

2381 "test.out": """\ 

2382 //[[[cog 

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

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

2385 //]]] 

2386 void DoSomething(); 

2387 void DoAnotherThing(); 

2388 void DoLastThing(); 

2389 //[[[end]]] 

2390 """, 

2391 } 

2392 

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

2394 # for Windows 

2395 cmd_w_args = "attrib -R %s" 

2396 cmd_w_asterisk = "attrib -R *" 

2397 else: 

2398 # for unix-like 

2399 cmd_w_args = "chmod +w %s" 

2400 cmd_w_asterisk = "chmod +w *" 

2401 

2402 def setUp(self): 

2403 super().setUp() 

2404 make_files(self.d) 

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

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

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

2408 

2409 def tearDown(self): 

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

2411 super().tearDown() 

2412 

2413 def test_readonly_no_command(self): 

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

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

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

2417 

2418 def test_readonly_with_command(self): 

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

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

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

2422 

2423 def test_readonly_with_command_with_no_slot(self): 

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

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

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

2427 

2428 def test_readonly_with_ineffectual_command(self): 

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

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

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

2432 

2433 

2434class ChecksumTests(TestCaseWithTempDir): 

2435 def test_create_checksum_output(self): 

2436 d = { 

2437 "cog1.txt": """\ 

2438 //[[[cog 

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

2440 //]]] 

2441 This line was generated. 

2442 //[[[end]]] what 

2443 This line was not. 

2444 """, 

2445 "cog1.out": """\ 

2446 //[[[cog 

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

2448 //]]] 

2449 This line was generated. 

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

2451 This line was not. 

2452 """, 

2453 } 

2454 

2455 make_files(d) 

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

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

2458 

2459 def test_check_checksum_output(self): 

2460 d = { 

2461 "cog1.txt": """\ 

2462 //[[[cog 

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

2464 cog.outl("generated by cog") 

2465 cog.outl("blah blah.") 

2466 //]]] 

2467 This line was generated. 

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

2469 """, 

2470 "cog1.out": """\ 

2471 //[[[cog 

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

2473 cog.outl("generated by cog") 

2474 cog.outl("blah blah.") 

2475 //]]] 

2476 This line was newly 

2477 generated by cog 

2478 blah blah. 

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

2480 """, 

2481 } 

2482 

2483 make_files(d) 

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

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

2486 

2487 def test_check_old_checksum_format(self): 

2488 # Test that old checksum format can still be read 

2489 d = { 

2490 "cog1.txt": """\ 

2491 //[[[cog 

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

2493 cog.outl("generated by cog") 

2494 cog.outl("blah blah.") 

2495 //]]] 

2496 This line was generated. 

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

2498 """, 

2499 "cog1.out": """\ 

2500 //[[[cog 

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

2502 cog.outl("generated by cog") 

2503 cog.outl("blah blah.") 

2504 //]]] 

2505 This line was newly 

2506 generated by cog 

2507 blah blah. 

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

2509 """, 

2510 } 

2511 

2512 make_files(d) 

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

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

2515 

2516 def test_remove_checksum_output(self): 

2517 d = { 

2518 "cog1.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 generated. 

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

2526 """, 

2527 "cog1.out": """\ 

2528 //[[[cog 

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

2530 cog.outl("generated by cog") 

2531 cog.outl("blah blah.") 

2532 //]]] 

2533 This line was newly 

2534 generated by cog 

2535 blah blah. 

2536 //[[[end]]] fooey 

2537 """, 

2538 } 

2539 

2540 make_files(d) 

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

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

2543 

2544 def test_tampered_checksum_output(self): 

2545 d = { 

2546 "cog1.txt": """\ 

2547 //[[[cog 

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

2549 cog.outl("generated by cog") 

2550 cog.outl("blah blah.") 

2551 //]]] 

2552 Xhis line was newly 

2553 generated by cog 

2554 blah blah. 

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

2556 """, 

2557 "cog2.txt": """\ 

2558 //[[[cog 

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

2560 cog.outl("generated by cog") 

2561 cog.outl("blah blah.") 

2562 //]]] 

2563 This line was newly 

2564 generated by cog 

2565 blah blah! 

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

2567 """, 

2568 "cog3.txt": """\ 

2569 //[[[cog 

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

2571 cog.outl("generated by cog") 

2572 cog.outl("blah blah.") 

2573 //]]] 

2574 

2575 This line was newly 

2576 generated by cog 

2577 blah blah. 

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

2579 """, 

2580 "cog4.txt": """\ 

2581 //[[[cog 

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

2583 cog.outl("generated by cog") 

2584 cog.outl("blah blah.") 

2585 //]]] 

2586 This line was newly 

2587 generated by cog 

2588 blah blah.. 

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

2590 """, 

2591 "cog5.txt": """\ 

2592 //[[[cog 

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

2594 cog.outl("generated by cog") 

2595 cog.outl("blah blah.") 

2596 //]]] 

2597 This line was newly 

2598 generated by cog 

2599 blah blah. 

2600 extra 

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

2602 """, 

2603 "cog6.txt": """\ 

2604 //[[[cog 

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

2606 cog.outl("generated by cog") 

2607 cog.outl("blah blah.") 

2608 //]]] 

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

2610 """, 

2611 } 

2612 

2613 make_files(d) 

2614 with self.assertRaisesRegex( 

2615 CogError, 

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

2617 ): 

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

2619 with self.assertRaisesRegex( 

2620 CogError, 

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

2622 ): 

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

2624 with self.assertRaisesRegex( 

2625 CogError, 

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

2627 ): 

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

2629 with self.assertRaisesRegex( 

2630 CogError, 

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

2632 ): 

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

2634 with self.assertRaisesRegex( 

2635 CogError, 

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

2637 ): 

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

2639 with self.assertRaisesRegex( 

2640 CogError, 

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

2642 ): 

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

2644 

2645 def test_argv_isnt_modified(self): 

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

2647 orig_argv = argv[:] 

2648 self.cog.callable_main(argv) 

2649 self.assertEqual(argv, orig_argv) 

2650 

2651 

2652class CustomMarkerTests(TestCaseWithTempDir): 

2653 def test_customer_markers(self): 

2654 d = { 

2655 "test.cog": """\ 

2656 //{{ 

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

2658 //}} 

2659 //{{end}} 

2660 """, 

2661 "test.out": """\ 

2662 //{{ 

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

2664 //}} 

2665 void MyFunction(); 

2666 //{{end}} 

2667 """, 

2668 } 

2669 

2670 make_files(d) 

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

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

2673 

2674 def test_truly_wacky_markers(self): 

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

2676 d = { 

2677 "test.cog": """\ 

2678 //**( 

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

2680 //**) 

2681 //**(end)** 

2682 """, 

2683 "test.out": """\ 

2684 //**( 

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

2686 //**) 

2687 void MyFunction(); 

2688 //**(end)** 

2689 """, 

2690 } 

2691 

2692 make_files(d) 

2693 self.cog.callable_main( 

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

2695 ) 

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

2697 

2698 def test_change_just_one_marker(self): 

2699 d = { 

2700 "test.cog": """\ 

2701 //**( 

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

2703 //]]] 

2704 //[[[end]]] 

2705 """, 

2706 "test.out": """\ 

2707 //**( 

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

2709 //]]] 

2710 void MyFunction(); 

2711 //[[[end]]] 

2712 """, 

2713 } 

2714 

2715 make_files(d) 

2716 self.cog.callable_main( 

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

2718 ) 

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

2720 

2721 

2722class BlakeTests(TestCaseWithTempDir): 

2723 # Blake Winton's contributions. 

2724 def test_delete_code(self): 

2725 # -o sets the output file. 

2726 d = { 

2727 "test.cog": """\ 

2728 // This is my C++ file. 

2729 //[[[cog 

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

2731 for fn in fnames: 

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

2733 //]]] 

2734 Some Sample Code Here 

2735 //[[[end]]]Data Data 

2736 And Some More 

2737 """, 

2738 "test.out": """\ 

2739 // This is my C++ file. 

2740 void DoSomething(); 

2741 void DoAnotherThing(); 

2742 void DoLastThing(); 

2743 And Some More 

2744 """, 

2745 } 

2746 

2747 make_files(d) 

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

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

2750 

2751 def test_delete_code_with_dash_r_fails(self): 

2752 d = { 

2753 "test.cog": """\ 

2754 // This is my C++ file. 

2755 """ 

2756 } 

2757 

2758 make_files(d) 

2759 with self.assertRaisesRegex( 

2760 CogUsageError, 

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

2762 ): 

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

2764 

2765 def test_setting_globals(self): 

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

2767 # processFile(). 

2768 d = { 

2769 "test.cog": """\ 

2770 // This is my C++ file. 

2771 //[[[cog 

2772 for fn in fnames: 

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

2774 //]]] 

2775 Some Sample Code Here 

2776 //[[[end]]]""", 

2777 "test.out": """\ 

2778 // This is my C++ file. 

2779 void DoBlake(); 

2780 void DoWinton(); 

2781 void DoContribution(); 

2782 """, 

2783 } 

2784 

2785 make_files(d) 

2786 globals = {} 

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

2788 self.cog.options.delete_code = True 

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

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

2791 

2792 

2793class ErrorCallTests(TestCaseWithTempDir): 

2794 def test_error_call_has_no_traceback(self): 

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

2796 d = { 

2797 "error.cog": """\ 

2798 //[[[cog 

2799 cog.error("Something Bad!") 

2800 //]]] 

2801 //[[[end]]] 

2802 """, 

2803 } 

2804 

2805 make_files(d) 

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

2807 output = self.output.getvalue() 

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

2809 

2810 def test_real_error_has_traceback(self): 

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

2812 d = { 

2813 "error.cog": """\ 

2814 //[[[cog 

2815 raise RuntimeError("Hey!") 

2816 //]]] 

2817 //[[[end]]] 

2818 """, 

2819 } 

2820 

2821 make_files(d) 

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

2823 output = self.output.getvalue() 

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

2825 self.assertTrue( 

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

2827 ) 

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

2829 

2830 

2831class HashHandlerTests(TestCase): 

2832 """Test cases for HashHandler functionality.""" 

2833 

2834 def setUp(self): 

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

2836 

2837 def test_validate_hash_with_base64_mismatch(self): 

2838 # Test the base64 validation branch with a mismatch 

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

2840 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346" 

2841 

2842 with self.assertRaises(ValueError) as cm: 

2843 self.handler.validate_hash(line, expected_hash) 

2844 self.assertEqual( 

2845 str(cm.exception), 

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

2847 ) 

2848 

2849 def test_validate_hash_with_base64_match(self): 

2850 # Test the base64 validation branch with a match 

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

2852 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346" 

2853 

2854 # Should not raise an exception 

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

2856 self.assertTrue(result) 

2857 

2858 

2859# Things not yet tested: 

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