Coverage for cogapp/test_cogapp.py: 29.63%

854 statements  

« prev     ^ index     » next       coverage.py v7.6.8, created at 2024-11-23 18:50 -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 usage, __version__, main 

18from .makefiles import make_files 

19from .whiteutils import reindent_block 

20 

21 

22class CogTestsInMemory(TestCase): 

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

24 

25 def test_no_cog(self): 

26 strings = [ 

27 "", 

28 " ", 

29 " \t \t \tx", 

30 "hello", 

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

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

33 ] 

34 for s in strings: 

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

36 

37 def test_simple(self): 

38 infile = """\ 

39 Some text. 

40 //[[[cog 

41 import cog 

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

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

44 //]]] 

45 gobbledegook. 

46 //[[[end]]] 

47 epilogue. 

48 """ 

49 

50 outfile = """\ 

51 Some text. 

52 //[[[cog 

53 import cog 

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

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

56 //]]] 

57 This is line one 

58 

59 This is line two 

60 //[[[end]]] 

61 epilogue. 

62 """ 

63 

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

65 

66 def test_empty_cog(self): 

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

68 # but it works. 

69 infile = """\ 

70 hello 

71 //[[[cog 

72 //]]] 

73 //[[[end]]] 

74 goodbye 

75 """ 

76 

77 infile = reindent_block(infile) 

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

79 

80 def test_multiple_cogs(self): 

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

82 infile = """\ 

83 //[[[cog 

84 cog.out("chunk1") 

85 //]]] 

86 chunk1 

87 //[[[end]]] 

88 //[[[cog 

89 cog.out("chunk2") 

90 //]]] 

91 chunk2 

92 //[[[end]]] 

93 between chunks 

94 //[[[cog 

95 cog.out("chunk3") 

96 //]]] 

97 chunk3 

98 //[[[end]]] 

99 """ 

100 

101 infile = reindent_block(infile) 

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

103 

104 def test_trim_blank_lines(self): 

105 infile = """\ 

106 //[[[cog 

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

108 cog.out(''' 

109 This is line two 

110 ''', dedent=True, trimblanklines=True) 

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

112 //]]] 

113 This is line one 

114 This is line two 

115 This is line three 

116 //[[[end]]] 

117 """ 

118 

119 infile = reindent_block(infile) 

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

121 

122 def test_trim_empty_blank_lines(self): 

123 infile = """\ 

124 //[[[cog 

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

126 cog.out(''' 

127 This is line two 

128 ''', dedent=True, trimblanklines=True) 

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

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

131 //]]] 

132 This is line one 

133 This is line two 

134 This is line three 

135 //[[[end]]] 

136 """ 

137 

138 infile = reindent_block(infile) 

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

140 

141 def test_trim_blank_lines_with_last_partial(self): 

142 infile = """\ 

143 //[[[cog 

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

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

146 //]]] 

147 This is line one 

148 Line two 

149 Line three 

150 //[[[end]]] 

151 """ 

152 

153 infile = reindent_block(infile) 

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

155 

156 def test_cog_out_dedent(self): 

157 infile = """\ 

158 //[[[cog 

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

160 cog.out(''' 

161 This is dedent=True 1 

162 This is dedent=True 2 

163 ''', dedent=True, trimblanklines=True) 

164 cog.out(''' 

165 This is dedent=False 1 

166 This is dedent=False 2 

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

168 cog.out(''' 

169 This is dedent=default 1 

170 This is dedent=default 2 

171 ''', trimblanklines=True) 

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

173 //]]] 

174 This is the first line 

175 This is dedent=True 1 

176 This is dedent=True 2 

177 This is dedent=False 1 

178 This is dedent=False 2 

179 This is dedent=default 1 

180 This is dedent=default 2 

181 This is the last line 

182 //[[[end]]] 

183 """ 

184 

185 infile = reindent_block(infile) 

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

187 

188 def test22_end_of_line(self): 

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

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

191 infile = """\ 

192 //[[[cog 

193 import cog 

194 for i in range(3): 

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

196 //]]] 

197 0 

198 1 

199 2 

200 //[[[end]]] 

201 """ 

202 

203 infile = reindent_block(infile) 

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

205 

206 def test_indented_code(self): 

207 infile = """\ 

208 first line 

209 [[[cog 

210 import cog 

211 for i in range(3): 

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

213 ]]] 

214 xx0 

215 xx1 

216 xx2 

217 [[[end]]] 

218 last line 

219 """ 

220 

221 infile = reindent_block(infile) 

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

223 

224 def test_prefixed_code(self): 

225 infile = """\ 

226 --[[[cog 

227 --import cog 

228 --for i in range(3): 

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

230 --]]] 

231 xx0 

232 xx1 

233 xx2 

234 --[[[end]]] 

235 """ 

236 

237 infile = reindent_block(infile) 

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

239 

240 def test_prefixed_indented_code(self): 

241 infile = """\ 

242 prologue 

243 --[[[cog 

244 -- import cog 

245 -- for i in range(3): 

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

247 --]]] 

248 xy0 

249 xy1 

250 xy2 

251 --[[[end]]] 

252 """ 

253 

254 infile = reindent_block(infile) 

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

256 

257 def test_bogus_prefix_match(self): 

258 infile = """\ 

259 prologue 

260 #[[[cog 

261 import cog 

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

263 for i in range(3): 

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

265 #]]] 

266 xy0 

267 xy1 

268 xy2 

269 #[[[end]]] 

270 """ 

271 

272 infile = reindent_block(infile) 

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

274 

275 def test_no_final_newline(self): 

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

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

278 infile = """\ 

279 prologue 

280 [[[cog 

281 import cog 

282 for i in range(3): 

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

284 ]]] 

285 012 

286 [[[end]]] 

287 epilogue 

288 """ 

289 

290 infile = reindent_block(infile) 

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

292 

293 def test_no_output_at_all(self): 

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

295 infile = """\ 

296 prologue 

297 [[[cog 

298 i = 1 

299 ]]] 

300 [[[end]]] 

301 epilogue 

302 """ 

303 

304 infile = reindent_block(infile) 

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

306 

307 def test_purely_blank_line(self): 

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

309 # prefix, that should be OK. 

310 

311 infile = """\ 

312 prologue 

313 [[[cog 

314 import sys 

315 cog.out("Hello") 

316 $ 

317 cog.out("There") 

318 ]]] 

319 HelloThere 

320 [[[end]]] 

321 epilogue 

322 """ 

323 

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

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

326 

327 def test_empty_outl(self): 

328 # Alexander Belchenko suggested the string argument to outl should 

329 # be optional. Does it work? 

330 

331 infile = """\ 

332 prologue 

333 [[[cog 

334 cog.outl("x") 

335 cog.outl() 

336 cog.outl("y") 

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

338 cog.outl(trimblanklines=True) 

339 cog.outl("z") 

340 ]]] 

341 x 

342 

343 y 

344 

345 z 

346 [[[end]]] 

347 epilogue 

348 """ 

349 

350 infile = reindent_block(infile) 

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

352 

353 def test_first_line_num(self): 

354 infile = """\ 

355 fooey 

356 [[[cog 

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

358 ]]] 

359 started at line number 2 

360 [[[end]]] 

361 blah blah 

362 [[[cog 

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

364 ]]] 

365 and again at line 8 

366 [[[end]]] 

367 """ 

368 

369 infile = reindent_block(infile) 

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

371 

372 def test_compact_one_line_code(self): 

373 infile = """\ 

374 first line 

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

376 get rid of this! 

377 [[[end]]] 

378 last line 

379 """ 

380 

381 outfile = """\ 

382 first line 

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

384 hello 81 

385 [[[end]]] 

386 last line 

387 """ 

388 

389 infile = reindent_block(infile) 

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

391 

392 def test_inside_out_compact(self): 

393 infile = """\ 

394 first line 

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

396 get rid of this! 

397 [[[end]]] 

398 last line 

399 """ 

400 with self.assertRaisesRegex( 

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

402 ): 

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

404 

405 def test_sharing_globals(self): 

406 infile = """\ 

407 first line 

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

409 [[[end]]] 

410 more literal junk. 

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

412 [[[end]]] 

413 last line 

414 """ 

415 

416 outfile = """\ 

417 first line 

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

419 [[[end]]] 

420 more literal junk. 

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

422 hey there 

423 [[[end]]] 

424 last line 

425 """ 

426 

427 infile = reindent_block(infile) 

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

429 

430 def test_assert_in_cog_code(self): 

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

432 infile = """\ 

433 [[[cog 

434 assert 1 == 2, "Oops" 

435 ]]] 

436 [[[end]]] 

437 """ 

438 infile = reindent_block(infile) 

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

440 Cog().process_string(infile) 

441 

442 def test_cog_previous(self): 

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

444 infile = """\ 

445 [[[cog 

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

447 cog.out(cog.previous) 

448 cog.outl("Ran again!") 

449 ]]] 

450 Hello there! 

451 [[[end]]] 

452 """ 

453 

454 outfile = """\ 

455 [[[cog 

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

457 cog.out(cog.previous) 

458 cog.outl("Ran again!") 

459 ]]] 

460 Hello there! 

461 Ran again! 

462 [[[end]]] 

463 """ 

464 

465 infile = reindent_block(infile) 

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

467 

468 

469class CogOptionsTests(TestCase): 

470 """Test the CogOptions class.""" 

471 

472 def test_equality(self): 

473 o = CogOptions() 

474 p = CogOptions() 

475 self.assertEqual(o, p) 

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

477 self.assertNotEqual(o, p) 

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

479 self.assertEqual(o, p) 

480 

481 def test_cloning(self): 

482 o = CogOptions() 

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

484 p = o.clone() 

485 self.assertEqual(o, p) 

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

487 self.assertNotEqual(o, p) 

488 q = CogOptions() 

489 q.parse_args( 

490 [ 

491 "-I", 

492 "fooey", 

493 "-I", 

494 "booey", 

495 "-s", 

496 " /*x*/", 

497 "-I", 

498 "huey", 

499 "-D", 

500 "foo=quux", 

501 ] 

502 ) 

503 self.assertEqual(p, q) 

504 

505 def test_combining_flags(self): 

506 # Single-character flags can be combined. 

507 o = CogOptions() 

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

509 p = CogOptions() 

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

511 self.assertEqual(o, p) 

512 

513 def test_markers(self): 

514 o = CogOptions() 

515 o._parse_markers("a b c") 

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

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

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

519 

520 def test_markers_switch(self): 

521 o = CogOptions() 

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

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

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

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

526 

527 

528class FileStructureTests(TestCase): 

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

530 

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

532 infile = reindent_block(infile) 

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

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

535 

536 def test_begin_no_end(self): 

537 infile = """\ 

538 Fooey 

539 #[[[cog 

540 cog.outl('hello') 

541 """ 

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

543 

544 def test_no_eoo(self): 

545 infile = """\ 

546 Fooey 

547 #[[[cog 

548 cog.outl('hello') 

549 #]]] 

550 """ 

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

552 

553 infile2 = """\ 

554 Fooey 

555 #[[[cog 

556 cog.outl('hello') 

557 #]]] 

558 #[[[cog 

559 cog.outl('goodbye') 

560 #]]] 

561 """ 

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

563 

564 def test_start_with_end(self): 

565 infile = """\ 

566 #]]] 

567 """ 

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

569 

570 infile2 = """\ 

571 #[[[cog 

572 cog.outl('hello') 

573 #]]] 

574 #[[[end]]] 

575 #]]] 

576 """ 

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

578 

579 def test_start_with_eoo(self): 

580 infile = """\ 

581 #[[[end]]] 

582 """ 

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

584 

585 infile2 = """\ 

586 #[[[cog 

587 cog.outl('hello') 

588 #]]] 

589 #[[[end]]] 

590 #[[[end]]] 

591 """ 

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

593 

594 def test_no_end(self): 

595 infile = """\ 

596 #[[[cog 

597 cog.outl("hello") 

598 #[[[end]]] 

599 """ 

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

601 

602 infile2 = """\ 

603 #[[[cog 

604 cog.outl('hello') 

605 #]]] 

606 #[[[end]]] 

607 #[[[cog 

608 cog.outl("hello") 

609 #[[[end]]] 

610 """ 

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

612 

613 def test_two_begins(self): 

614 infile = """\ 

615 #[[[cog 

616 #[[[cog 

617 cog.outl("hello") 

618 #]]] 

619 #[[[end]]] 

620 """ 

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

622 

623 infile2 = """\ 

624 #[[[cog 

625 cog.outl("hello") 

626 #]]] 

627 #[[[end]]] 

628 #[[[cog 

629 #[[[cog 

630 cog.outl("hello") 

631 #]]] 

632 #[[[end]]] 

633 """ 

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

635 

636 def test_two_ends(self): 

637 infile = """\ 

638 #[[[cog 

639 cog.outl("hello") 

640 #]]] 

641 #]]] 

642 #[[[end]]] 

643 """ 

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

645 

646 infile2 = """\ 

647 #[[[cog 

648 cog.outl("hello") 

649 #]]] 

650 #[[[end]]] 

651 #[[[cog 

652 cog.outl("hello") 

653 #]]] 

654 #]]] 

655 #[[[end]]] 

656 """ 

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

658 

659 

660class CogErrorTests(TestCase): 

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

662 

663 def test_error_msg(self): 

664 infile = """\ 

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

666 [[[end]]] 

667 """ 

668 

669 infile = reindent_block(infile) 

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

671 Cog().process_string(infile) 

672 

673 def test_error_no_msg(self): 

674 infile = """\ 

675 [[[cog cog.error()]]] 

676 [[[end]]] 

677 """ 

678 

679 infile = reindent_block(infile) 

680 with self.assertRaisesRegex( 

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

682 ): 

683 Cog().process_string(infile) 

684 

685 def test_no_error_if_error_not_called(self): 

686 infile = """\ 

687 --[[[cog 

688 --import cog 

689 --for i in range(3): 

690 -- if i > 10: 

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

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

693 --]]] 

694 xx0 

695 xx1 

696 xx2 

697 --[[[end]]] 

698 """ 

699 

700 infile = reindent_block(infile) 

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

702 

703 

704class CogGeneratorGetCodeTests(TestCase): 

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

706 

707 def setUp(self): 

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

709 # the functions we're going to use. 

710 self.gen = CogGenerator() 

711 self.m = self.gen.parse_marker 

712 self.parse_line = self.gen.parse_line 

713 

714 def test_empty(self): 

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

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

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

718 

719 def test_simple(self): 

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

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

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

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

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

725 

726 def test_compressed1(self): 

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

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

729 self.parse_line("// hello") 

730 self.parse_line("// bye") 

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

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

733 

734 def test_compressed2(self): 

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

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

737 self.parse_line("hello") 

738 self.parse_line("bye") 

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

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

741 

742 def test_compressed3(self): 

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

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

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

746 self.parse_line("bye") 

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

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

749 

750 def test_compressed4(self): 

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

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

753 self.parse_line("hello") 

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

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

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

757 

758 def test_no_common_prefix_for_markers(self): 

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

760 # C++ compiler. 

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

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

763 self.parse_line("") 

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

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

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

767 

768 

769class TestCaseWithTempDir(TestCase): 

770 def new_cog(self): 

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

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

773 self.cog = Cog() 

774 self.output = io.StringIO() 

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

776 

777 def setUp(self): 

778 # Create a temporary directory. 

779 self.tempdir = os.path.join( 

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

781 ) 

782 os.mkdir(self.tempdir) 

783 self.olddir = os.getcwd() 

784 os.chdir(self.tempdir) 

785 self.new_cog() 

786 

787 def tearDown(self): 

788 os.chdir(self.olddir) 

789 # Get rid of the temporary directory. 

790 shutil.rmtree(self.tempdir) 

791 

792 def assertFilesSame(self, file_name1, file_name2): 

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

794 text1 = f1.read() 

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

796 text2 = f2.read() 

797 self.assertEqual(text1, text2) 

798 

799 def assertFileContent(self, fname, content): 

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

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

802 file_content = f.read() 

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

804 

805 

806class ArgumentHandlingTests(TestCaseWithTempDir): 

807 def test_argument_failure(self): 

808 # Return value 2 means usage problem. 

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

810 output = self.output.getvalue() 

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

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

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

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

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

816 

817 def test_no_dash_o_and_at_file(self): 

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

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

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

821 

822 def test_no_dash_o_and_amp_file(self): 

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

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

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

826 

827 def test_dash_v(self): 

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

829 output = self.output.getvalue() 

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

831 

832 def produces_help(self, args): 

833 self.new_cog() 

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

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

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

837 

838 def test_dash_h(self): 

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

840 self.produces_help("-h") 

841 self.produces_help("-?") 

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

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

844 

845 def test_dash_o_and_dash_r(self): 

846 d = { 

847 "cogfile.txt": """\ 

848 # Please run cog 

849 """ 

850 } 

851 

852 make_files(d) 

853 with self.assertRaisesRegex( 

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

855 ): 

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

857 

858 def test_dash_z(self): 

859 d = { 

860 "test.cog": """\ 

861 // This is my C++ file. 

862 //[[[cog 

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

864 for fn in fnames: 

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

866 //]]] 

867 """, 

868 "test.out": """\ 

869 // This is my C++ file. 

870 //[[[cog 

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

872 for fn in fnames: 

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

874 //]]] 

875 void DoSomething(); 

876 void DoAnotherThing(); 

877 void DoLastThing(); 

878 """, 

879 } 

880 

881 make_files(d) 

882 with self.assertRaisesRegex( 

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

884 ): 

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

886 self.new_cog() 

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

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

889 

890 def test_bad_dash_d(self): 

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

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

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

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

895 

896 def test_bad_markers(self): 

897 with self.assertRaisesRegex( 

898 CogUsageError, 

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

900 ): 

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

902 with self.assertRaisesRegex( 

903 CogUsageError, 

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

905 ): 

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

907 

908 

909class TestMain(TestCaseWithTempDir): 

910 def setUp(self): 

911 super().setUp() 

912 self.old_argv = sys.argv[:] 

913 self.old_stderr = sys.stderr 

914 sys.stderr = io.StringIO() 

915 

916 def tearDown(self): 

917 sys.stderr = self.old_stderr 

918 sys.argv = self.old_argv 

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

920 super().tearDown() 

921 

922 def test_main_function(self): 

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

924 ret = main() 

925 self.assertEqual(ret, 2) 

926 stderr = sys.stderr.getvalue() 

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

928 

929 files = { 

930 "test.cog": """\ 

931 //[[[cog 

932 def func(): 

933 import mycode 

934 mycode.boom() 

935 //]]] 

936 //[[[end]]] 

937 ----- 

938 //[[[cog 

939 func() 

940 //]]] 

941 //[[[end]]] 

942 """, 

943 "mycode.py": """\ 

944 def boom(): 

945 [][0] 

946 """, 

947 } 

948 

949 def test_error_report(self): 

950 self.check_error_report() 

951 

952 def test_error_report_with_prologue(self): 

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

954 

955 def check_error_report(self, *args): 

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

957 make_files(self.files) 

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

959 main() 

960 expected = reindent_block("""\ 

961 Traceback (most recent call last): 

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

963 func() 

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

965 mycode.boom() 

966 File "MYCODE", line 2, in boom 

967 [][0] 

968 IndexError: list index out of range 

969 """) 

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

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

972 

973 def test_error_in_prologue(self): 

974 make_files(self.files) 

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

976 main() 

977 expected = reindent_block("""\ 

978 Traceback (most recent call last): 

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

980 import mycode; mycode.boom() 

981 File "MYCODE", line 2, in boom 

982 [][0] 

983 IndexError: list index out of range 

984 """) 

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

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

987 

988 

989class TestFileHandling(TestCaseWithTempDir): 

990 def test_simple(self): 

991 d = { 

992 "test.cog": """\ 

993 // This is my C++ file. 

994 //[[[cog 

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

996 for fn in fnames: 

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

998 //]]] 

999 //[[[end]]] 

1000 """, 

1001 "test.out": """\ 

1002 // This is my C++ file. 

1003 //[[[cog 

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

1005 for fn in fnames: 

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

1007 //]]] 

1008 void DoSomething(); 

1009 void DoAnotherThing(); 

1010 void DoLastThing(); 

1011 //[[[end]]] 

1012 """, 

1013 } 

1014 

1015 make_files(d) 

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

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

1018 output = self.output.getvalue() 

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

1020 

1021 def test_print_output(self): 

1022 d = { 

1023 "test.cog": """\ 

1024 // This is my C++ file. 

1025 //[[[cog 

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

1027 for fn in fnames: 

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

1029 //]]] 

1030 //[[[end]]] 

1031 """, 

1032 "test.out": """\ 

1033 // This is my C++ file. 

1034 //[[[cog 

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

1036 for fn in fnames: 

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

1038 //]]] 

1039 void DoSomething(); 

1040 void DoAnotherThing(); 

1041 void DoLastThing(); 

1042 //[[[end]]] 

1043 """, 

1044 } 

1045 

1046 make_files(d) 

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

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

1049 output = self.output.getvalue() 

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

1051 

1052 def test_wildcards(self): 

1053 d = { 

1054 "test.cog": """\ 

1055 // This is my C++ file. 

1056 //[[[cog 

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

1058 for fn in fnames: 

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

1060 //]]] 

1061 //[[[end]]] 

1062 """, 

1063 "test2.cog": """\ 

1064 // This is my C++ file. 

1065 //[[[cog 

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

1067 for fn in fnames: 

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

1069 //]]] 

1070 //[[[end]]] 

1071 """, 

1072 "test.out": """\ 

1073 // This is my C++ file. 

1074 //[[[cog 

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

1076 for fn in fnames: 

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

1078 //]]] 

1079 void DoSomething(); 

1080 void DoAnotherThing(); 

1081 void DoLastThing(); 

1082 //[[[end]]] 

1083 """, 

1084 "not_this_one.cog": """\ 

1085 // This is my C++ file. 

1086 //[[[cog 

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

1088 for fn in fnames: 

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

1090 //]]] 

1091 //[[[end]]] 

1092 """, 

1093 "not_this_one.out": """\ 

1094 // This is my C++ file. 

1095 //[[[cog 

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

1097 for fn in fnames: 

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

1099 //]]] 

1100 //[[[end]]] 

1101 """, 

1102 } 

1103 

1104 make_files(d) 

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

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

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

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

1109 output = self.output.getvalue() 

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

1111 

1112 def test_output_file(self): 

1113 # -o sets the output file. 

1114 d = { 

1115 "test.cog": """\ 

1116 // This is my C++ file. 

1117 //[[[cog 

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

1119 for fn in fnames: 

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

1121 //]]] 

1122 //[[[end]]] 

1123 """, 

1124 "test.out": """\ 

1125 // This is my C++ file. 

1126 //[[[cog 

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

1128 for fn in fnames: 

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

1130 //]]] 

1131 void DoSomething(); 

1132 void DoAnotherThing(); 

1133 void DoLastThing(); 

1134 //[[[end]]] 

1135 """, 

1136 } 

1137 

1138 make_files(d) 

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

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

1141 

1142 def test_at_file(self): 

1143 d = { 

1144 "one.cog": """\ 

1145 //[[[cog 

1146 cog.outl("hello world") 

1147 //]]] 

1148 //[[[end]]] 

1149 """, 

1150 "one.out": """\ 

1151 //[[[cog 

1152 cog.outl("hello world") 

1153 //]]] 

1154 hello world 

1155 //[[[end]]] 

1156 """, 

1157 "two.cog": """\ 

1158 //[[[cog 

1159 cog.outl("goodbye cruel world") 

1160 //]]] 

1161 //[[[end]]] 

1162 """, 

1163 "two.out": """\ 

1164 //[[[cog 

1165 cog.outl("goodbye cruel world") 

1166 //]]] 

1167 goodbye cruel world 

1168 //[[[end]]] 

1169 """, 

1170 "cogfiles.txt": """\ 

1171 # Please run cog 

1172 one.cog 

1173 

1174 two.cog 

1175 """, 

1176 } 

1177 

1178 make_files(d) 

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

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

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

1182 output = self.output.getvalue() 

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

1184 

1185 def test_nested_at_file(self): 

1186 d = { 

1187 "one.cog": """\ 

1188 //[[[cog 

1189 cog.outl("hello world") 

1190 //]]] 

1191 //[[[end]]] 

1192 """, 

1193 "one.out": """\ 

1194 //[[[cog 

1195 cog.outl("hello world") 

1196 //]]] 

1197 hello world 

1198 //[[[end]]] 

1199 """, 

1200 "two.cog": """\ 

1201 //[[[cog 

1202 cog.outl("goodbye cruel world") 

1203 //]]] 

1204 //[[[end]]] 

1205 """, 

1206 "two.out": """\ 

1207 //[[[cog 

1208 cog.outl("goodbye cruel world") 

1209 //]]] 

1210 goodbye cruel world 

1211 //[[[end]]] 

1212 """, 

1213 "cogfiles.txt": """\ 

1214 # Please run cog 

1215 one.cog 

1216 @cogfiles2.txt 

1217 """, 

1218 "cogfiles2.txt": """\ 

1219 # This one too, please. 

1220 two.cog 

1221 """, 

1222 } 

1223 

1224 make_files(d) 

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

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

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

1228 output = self.output.getvalue() 

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

1230 

1231 def test_at_file_with_args(self): 

1232 d = { 

1233 "both.cog": """\ 

1234 //[[[cog 

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

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

1237 //]]] 

1238 //[[[end]]] 

1239 """, 

1240 "one.out": """\ 

1241 //[[[cog 

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

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

1244 //]]] 

1245 one: True // ONE 

1246 two: False // ONE 

1247 //[[[end]]] 

1248 """, 

1249 "two.out": """\ 

1250 //[[[cog 

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

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

1253 //]]] 

1254 one: False // TWO 

1255 two: True // TWO 

1256 //[[[end]]] 

1257 """, 

1258 "cogfiles.txt": """\ 

1259 # Please run cog 

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

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

1262 """, 

1263 } 

1264 

1265 make_files(d) 

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

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

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

1269 

1270 def test_at_file_with_bad_arg_combo(self): 

1271 d = { 

1272 "both.cog": """\ 

1273 //[[[cog 

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

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

1276 //]]] 

1277 //[[[end]]] 

1278 """, 

1279 "cogfiles.txt": """\ 

1280 # Please run cog 

1281 both.cog 

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

1283 """, 

1284 } 

1285 

1286 make_files(d) 

1287 with self.assertRaisesRegex( 

1288 CogUsageError, 

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

1290 ): 

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

1292 

1293 def test_at_file_with_tricky_filenames(self): 

1294 def fix_backslashes(files_txt): 

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

1296 if sys.platform != "win32": 

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

1298 return files_txt 

1299 

1300 d = { 

1301 "one 1.cog": """\ 

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

1303 """, 

1304 "one.out": """\ 

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

1306 hello world //xxx 

1307 """, 

1308 "subdir": { 

1309 "subback.cog": """\ 

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

1311 """, 

1312 "subfwd.cog": """\ 

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

1314 """, 

1315 }, 

1316 "subback.out": """\ 

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

1318 down deep with backslashes //yyy 

1319 """, 

1320 "subfwd.out": """\ 

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

1322 down deep with slashes //zzz 

1323 """, 

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

1325 # Please run cog 

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

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

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

1329 """), 

1330 } 

1331 

1332 make_files(d) 

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

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

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

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

1337 

1338 def test_amp_file(self): 

1339 d = { 

1340 "code": { 

1341 "files_to_cog": """\ 

1342 # A locally resolved file name. 

1343 test.cog 

1344 """, 

1345 "test.cog": """\ 

1346 //[[[cog 

1347 import myampsubmodule 

1348 //]]] 

1349 //[[[end]]] 

1350 """, 

1351 "test.out": """\ 

1352 //[[[cog 

1353 import myampsubmodule 

1354 //]]] 

1355 Hello from myampsubmodule 

1356 //[[[end]]] 

1357 """, 

1358 "myampsubmodule.py": """\ 

1359 import cog 

1360 cog.outl("Hello from myampsubmodule") 

1361 """, 

1362 } 

1363 } 

1364 

1365 make_files(d) 

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

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

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

1369 

1370 def run_with_verbosity(self, verbosity): 

1371 d = { 

1372 "unchanged.cog": """\ 

1373 //[[[cog 

1374 cog.outl("hello world") 

1375 //]]] 

1376 hello world 

1377 //[[[end]]] 

1378 """, 

1379 "changed.cog": """\ 

1380 //[[[cog 

1381 cog.outl("goodbye cruel world") 

1382 //]]] 

1383 //[[[end]]] 

1384 """, 

1385 "cogfiles.txt": """\ 

1386 unchanged.cog 

1387 changed.cog 

1388 """, 

1389 } 

1390 

1391 make_files(d) 

1392 self.cog.callable_main( 

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

1394 ) 

1395 output = self.output.getvalue() 

1396 return output 

1397 

1398 def test_verbosity0(self): 

1399 output = self.run_with_verbosity("0") 

1400 self.assertEqual(output, "") 

1401 

1402 def test_verbosity1(self): 

1403 output = self.run_with_verbosity("1") 

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

1405 

1406 def test_verbosity2(self): 

1407 output = self.run_with_verbosity("2") 

1408 self.assertEqual( 

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

1410 ) 

1411 

1412 

1413class CogTestLineEndings(TestCaseWithTempDir): 

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

1415 

1416 lines_in = [ 

1417 "Some text.", 

1418 "//[[[cog", 

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

1420 "//]]]", 

1421 "gobbledegook.", 

1422 "//[[[end]]]", 

1423 "epilogue.", 

1424 "", 

1425 ] 

1426 

1427 lines_out = [ 

1428 "Some text.", 

1429 "//[[[cog", 

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

1431 "//]]]", 

1432 "Cog text", 

1433 "//[[[end]]]", 

1434 "epilogue.", 

1435 "", 

1436 ] 

1437 

1438 def test_output_native_eol(self): 

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

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

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

1442 

1443 def test_output_lf_eol(self): 

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

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

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

1447 

1448 def test_replace_native_eol(self): 

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

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

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

1452 

1453 def test_replace_lf_eol(self): 

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

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

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

1457 

1458 

1459class CogTestCharacterEncoding(TestCaseWithTempDir): 

1460 def test_simple(self): 

1461 d = { 

1462 "test.cog": b"""\ 

1463 // This is my C++ file. 

1464 //[[[cog 

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

1466 //]]] 

1467 //[[[end]]] 

1468 """, 

1469 "test.out": b"""\ 

1470 // This is my C++ file. 

1471 //[[[cog 

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

1473 //]]] 

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

1475 //[[[end]]] 

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

1477 } 

1478 

1479 make_files(d) 

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

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

1482 output = self.output.getvalue() 

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

1484 

1485 def test_file_encoding_option(self): 

1486 d = { 

1487 "test.cog": b"""\ 

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

1489 //[[[cog 

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

1491 //]]] 

1492 //[[[end]]] 

1493 """, 

1494 "test.out": b"""\ 

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

1496 //[[[cog 

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

1498 //]]] 

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

1500 //[[[end]]] 

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

1502 } 

1503 

1504 make_files(d) 

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

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

1507 output = self.output.getvalue() 

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

1509 

1510 

1511class TestCaseWithImports(TestCaseWithTempDir): 

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

1513 

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

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

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

1517 

1518 """ 

1519 

1520 def setUp(self): 

1521 super().setUp() 

1522 self.sysmodulekeys = list(sys.modules) 

1523 

1524 def tearDown(self): 

1525 modstoscrub = [ 

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

1527 ] 

1528 for modname in modstoscrub: 

1529 del sys.modules[modname] 

1530 super().tearDown() 

1531 

1532 

1533class CogIncludeTests(TestCaseWithImports): 

1534 dincludes = { 

1535 "test.cog": """\ 

1536 //[[[cog 

1537 import mymodule 

1538 //]]] 

1539 //[[[end]]] 

1540 """, 

1541 "test.out": """\ 

1542 //[[[cog 

1543 import mymodule 

1544 //]]] 

1545 Hello from mymodule 

1546 //[[[end]]] 

1547 """, 

1548 "test2.out": """\ 

1549 //[[[cog 

1550 import mymodule 

1551 //]]] 

1552 Hello from mymodule in inc2 

1553 //[[[end]]] 

1554 """, 

1555 "include": { 

1556 "mymodule.py": """\ 

1557 import cog 

1558 cog.outl("Hello from mymodule") 

1559 """ 

1560 }, 

1561 "inc2": { 

1562 "mymodule.py": """\ 

1563 import cog 

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

1565 """ 

1566 }, 

1567 "inc3": { 

1568 "someothermodule.py": """\ 

1569 import cog 

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

1571 """ 

1572 }, 

1573 } 

1574 

1575 def test_need_include_path(self): 

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

1577 make_files(self.dincludes) 

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

1579 with self.assertRaisesRegex(CogUserException, msg): 

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

1581 

1582 def test_include_path(self): 

1583 # Test that -I adds include directories properly. 

1584 make_files(self.dincludes) 

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

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

1587 

1588 def test_two_include_paths(self): 

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

1590 make_files(self.dincludes) 

1591 self.cog.callable_main( 

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

1593 ) 

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

1595 

1596 def test_two_include_paths2(self): 

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

1598 make_files(self.dincludes) 

1599 self.cog.callable_main( 

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

1601 ) 

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

1603 

1604 def test_useless_include_path(self): 

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

1606 make_files(self.dincludes) 

1607 self.cog.callable_main( 

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

1609 ) 

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

1611 

1612 def test_sys_path_is_unchanged(self): 

1613 d = { 

1614 "bad.cog": """\ 

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

1616 //[[[end]]] 

1617 """, 

1618 "good.cog": """\ 

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

1620 //[[[end]]] 

1621 """, 

1622 } 

1623 

1624 make_files(d) 

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

1626 oldsyspath = sys.path[:] 

1627 self.new_cog() 

1628 self.assertEqual(oldsyspath, sys.path) 

1629 # Is it unchanged for a successful run? 

1630 self.new_cog() 

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

1632 self.assertEqual(oldsyspath, sys.path) 

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

1634 self.new_cog() 

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

1636 self.assertEqual(oldsyspath, sys.path) 

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

1638 self.new_cog() 

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

1640 self.assertEqual(oldsyspath, sys.path) 

1641 # Is it unchanged for a failed run? 

1642 self.new_cog() 

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

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

1645 self.assertEqual(oldsyspath, sys.path) 

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

1647 self.new_cog() 

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

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

1650 self.assertEqual(oldsyspath, sys.path) 

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

1652 self.new_cog() 

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

1654 self.cog.callable_main( 

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

1656 ) 

1657 self.assertEqual(oldsyspath, sys.path) 

1658 

1659 def test_sub_directories(self): 

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

1661 

1662 d = { 

1663 "code": { 

1664 "test.cog": """\ 

1665 //[[[cog 

1666 import mysubmodule 

1667 //]]] 

1668 //[[[end]]] 

1669 """, 

1670 "test.out": """\ 

1671 //[[[cog 

1672 import mysubmodule 

1673 //]]] 

1674 Hello from mysubmodule 

1675 //[[[end]]] 

1676 """, 

1677 "mysubmodule.py": """\ 

1678 import cog 

1679 cog.outl("Hello from mysubmodule") 

1680 """, 

1681 } 

1682 } 

1683 

1684 make_files(d) 

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

1686 # auto-include the current directory 

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

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

1689 

1690 

1691class CogTestsInFiles(TestCaseWithTempDir): 

1692 def test_warn_if_no_cog_code(self): 

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

1694 d = { 

1695 "with.cog": """\ 

1696 //[[[cog 

1697 cog.outl("hello world") 

1698 //]]] 

1699 hello world 

1700 //[[[end]]] 

1701 """, 

1702 "without.cog": """\ 

1703 There's no cog 

1704 code in this file. 

1705 """, 

1706 } 

1707 

1708 make_files(d) 

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

1710 output = self.output.getvalue() 

1711 self.assertNotIn("Warning", output) 

1712 self.new_cog() 

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

1714 output = self.output.getvalue() 

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

1716 self.new_cog() 

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

1718 output = self.output.getvalue() 

1719 self.assertNotIn("Warning", output) 

1720 

1721 def test_file_name_props(self): 

1722 d = { 

1723 "cog1.txt": """\ 

1724 //[[[cog 

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

1726 //]]] 

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

1728 [[[end]]] 

1729 """, 

1730 "cog1.out": """\ 

1731 //[[[cog 

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

1733 //]]] 

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

1735 [[[end]]] 

1736 """, 

1737 "cog1out.out": """\ 

1738 //[[[cog 

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

1740 //]]] 

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

1742 [[[end]]] 

1743 """, 

1744 } 

1745 

1746 make_files(d) 

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

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

1749 self.new_cog() 

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

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

1752 

1753 def test_globals_dont_cross_files(self): 

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

1755 d = { 

1756 "one.cog": """\ 

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

1758 //[[[end]]] 

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

1760 //[[[end]]] 

1761 """, 

1762 "one.out": """\ 

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

1764 //[[[end]]] 

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

1766 This was set in one.cog 

1767 //[[[end]]] 

1768 """, 

1769 "two.cog": """\ 

1770 //[[[cog 

1771 try: 

1772 cog.outl(s) 

1773 except NameError: 

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

1775 //]]] 

1776 //[[[end]]] 

1777 """, 

1778 "two.out": """\ 

1779 //[[[cog 

1780 try: 

1781 cog.outl(s) 

1782 except NameError: 

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

1784 //]]] 

1785 s isn't set! 

1786 //[[[end]]] 

1787 """, 

1788 "cogfiles.txt": """\ 

1789 # Please run cog 

1790 one.cog 

1791 

1792 two.cog 

1793 """, 

1794 } 

1795 

1796 make_files(d) 

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

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

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

1800 output = self.output.getvalue() 

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

1802 

1803 def test_remove_generated_output(self): 

1804 d = { 

1805 "cog1.txt": """\ 

1806 //[[[cog 

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

1808 //]]] 

1809 This line was generated. 

1810 //[[[end]]] 

1811 This line was not. 

1812 """, 

1813 "cog1.out": """\ 

1814 //[[[cog 

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

1816 //]]] 

1817 //[[[end]]] 

1818 This line was not. 

1819 """, 

1820 "cog1.out2": """\ 

1821 //[[[cog 

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

1823 //]]] 

1824 This line was generated. 

1825 //[[[end]]] 

1826 This line was not. 

1827 """, 

1828 } 

1829 

1830 make_files(d) 

1831 # Remove generated output. 

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

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

1834 self.new_cog() 

1835 # Regenerate the generated output. 

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

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

1838 self.new_cog() 

1839 # Remove the generated output again. 

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

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

1842 

1843 def test_msg_call(self): 

1844 infile = """\ 

1845 #[[[cog 

1846 cog.msg("Hello there!") 

1847 #]]] 

1848 #[[[end]]] 

1849 """ 

1850 infile = reindent_block(infile) 

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

1852 output = self.output.getvalue() 

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

1854 

1855 def test_error_message_has_no_traceback(self): 

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

1857 

1858 d = { 

1859 "cog1.txt": """\ 

1860 //[[[cog 

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

1862 cog.outl("generated by cog") 

1863 cog.outl("blah blah.") 

1864 //]]] 

1865 Xhis line was newly 

1866 generated by cog 

1867 blah blah. 

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

1869 """, 

1870 } 

1871 

1872 make_files(d) 

1873 stderr = io.StringIO() 

1874 self.cog.set_output(stderr=stderr) 

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

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

1877 self.assertEqual( 

1878 stderr.getvalue(), 

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

1880 ) 

1881 

1882 def test_dash_d(self): 

1883 d = { 

1884 "test.cog": """\ 

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

1886 --[[[end]]] 

1887 """, 

1888 "test.kablooey": """\ 

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

1890 Defined fooey as kablooey 

1891 --[[[end]]] 

1892 """, 

1893 "test.einstein": """\ 

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

1895 Defined fooey as e=mc2 

1896 --[[[end]]] 

1897 """, 

1898 } 

1899 

1900 make_files(d) 

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

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

1903 make_files(d) 

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

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

1906 make_files(d) 

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

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

1909 make_files(d) 

1910 self.cog.callable_main( 

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

1912 ) 

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

1914 make_files(d) 

1915 self.cog.callable_main( 

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

1917 ) 

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

1919 make_files(d) 

1920 self.cog.callable_main( 

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

1922 ) 

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

1924 

1925 def test_output_to_stdout(self): 

1926 d = { 

1927 "test.cog": """\ 

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

1929 --[[[end]]] 

1930 """ 

1931 } 

1932 

1933 make_files(d) 

1934 stderr = io.StringIO() 

1935 self.cog.set_output(stderr=stderr) 

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

1937 output = self.output.getvalue() 

1938 outerr = stderr.getvalue() 

1939 self.assertEqual( 

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

1941 ) 

1942 self.assertEqual(outerr, "") 

1943 

1944 def test_read_from_stdin(self): 

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

1946 

1947 def restore_stdin(old_stdin): 

1948 sys.stdin = old_stdin 

1949 

1950 self.addCleanup(restore_stdin, sys.stdin) 

1951 sys.stdin = stdin 

1952 

1953 stderr = io.StringIO() 

1954 self.cog.set_output(stderr=stderr) 

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

1956 output = self.output.getvalue() 

1957 outerr = stderr.getvalue() 

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

1959 self.assertEqual(outerr, "") 

1960 

1961 def test_suffix_output_lines(self): 

1962 d = { 

1963 "test.cog": """\ 

1964 Hey there. 

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

1966 ;[[[end]]] 

1967 Good bye. 

1968 """, 

1969 "test.out": """\ 

1970 Hey there. 

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

1972 a (foo) 

1973 b (foo) 

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

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

1976 """ 

1977 c (foo) 

1978 ;[[[end]]] 

1979 Good bye. 

1980 """, 

1981 } 

1982 

1983 make_files(d) 

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

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

1986 

1987 def test_empty_suffix(self): 

1988 d = { 

1989 "test.cog": """\ 

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

1991 ;[[[end]]] 

1992 """, 

1993 "test.out": """\ 

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

1995 a 

1996 b 

1997 c 

1998 ;[[[end]]] 

1999 """, 

2000 } 

2001 

2002 make_files(d) 

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

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

2005 

2006 def test_hellish_suffix(self): 

2007 d = { 

2008 "test.cog": """\ 

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

2010 """, 

2011 "test.out": """\ 

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

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

2014 

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

2016 """, 

2017 } 

2018 

2019 make_files(d) 

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

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

2022 

2023 def test_prologue(self): 

2024 d = { 

2025 "test.cog": """\ 

2026 Some text. 

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

2028 //[[[end]]] 

2029 epilogue. 

2030 """, 

2031 "test.out": """\ 

2032 Some text. 

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

2034 1.4142135623 

2035 //[[[end]]] 

2036 epilogue. 

2037 """, 

2038 } 

2039 

2040 make_files(d) 

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

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

2043 

2044 def test_threads(self): 

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

2046 # different threads. 

2047 numthreads = 20 

2048 

2049 d = {} 

2050 for i in range(numthreads): 

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

2052 "x\n" * i 

2053 + "[[[cog\n" 

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

2055 + "]]]\n" 

2056 + "[[[end]]]\n" 

2057 ) 

2058 make_files(d) 

2059 

2060 results = [] 

2061 

2062 def thread_main(num): 

2063 try: 

2064 ret = Cog().main( 

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

2066 ) 

2067 assert ret == 0 

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

2069 results.append(exc) 

2070 else: 

2071 results.append(None) 

2072 

2073 ts = [ 

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

2075 ] 

2076 for t in ts: 

2077 t.start() 

2078 for t in ts: 

2079 t.join() 

2080 assert results == [None] * numthreads 

2081 

2082 

2083class CheckTests(TestCaseWithTempDir): 

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

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

2086 print(self.output.getvalue()) 

2087 self.assertEqual(status, actual_status) 

2088 

2089 def assert_made_files_unchanged(self, d): 

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

2091 content = reindent_block(content) 

2092 if os.name == "nt": 

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

2094 self.assertFileContent(name, content) 

2095 

2096 def test_check_no_cog(self): 

2097 d = { 

2098 "hello.txt": """\ 

2099 Hello. 

2100 """, 

2101 } 

2102 make_files(d) 

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

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

2105 self.assert_made_files_unchanged(d) 

2106 

2107 def test_check_good(self): 

2108 d = { 

2109 "unchanged.cog": """\ 

2110 //[[[cog 

2111 cog.outl("hello world") 

2112 //]]] 

2113 hello world 

2114 //[[[end]]] 

2115 """, 

2116 } 

2117 make_files(d) 

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

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

2120 self.assert_made_files_unchanged(d) 

2121 

2122 def test_check_bad(self): 

2123 d = { 

2124 "changed.cog": """\ 

2125 //[[[cog 

2126 cog.outl("goodbye world") 

2127 //]]] 

2128 hello world 

2129 //[[[end]]] 

2130 """, 

2131 } 

2132 make_files(d) 

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

2134 self.assertEqual( 

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

2136 ) 

2137 self.assert_made_files_unchanged(d) 

2138 

2139 def test_check_mixed(self): 

2140 d = { 

2141 "unchanged.cog": """\ 

2142 //[[[cog 

2143 cog.outl("hello world") 

2144 //]]] 

2145 hello world 

2146 //[[[end]]] 

2147 """, 

2148 "changed.cog": """\ 

2149 //[[[cog 

2150 cog.outl("goodbye world") 

2151 //]]] 

2152 hello world 

2153 //[[[end]]] 

2154 """, 

2155 } 

2156 make_files(d) 

2157 for verbosity, output in [ 

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

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

2160 ( 

2161 "2", 

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

2163 ), 

2164 ]: 

2165 self.new_cog() 

2166 self.run_check( 

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

2168 ) 

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

2170 self.assert_made_files_unchanged(d) 

2171 

2172 def test_check_with_good_checksum(self): 

2173 d = { 

2174 "good.txt": """\ 

2175 //[[[cog 

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

2177 cog.outl("generated by cog") 

2178 cog.outl("blah blah.") 

2179 //]]] 

2180 This line was newly 

2181 generated by cog 

2182 blah blah. 

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

2184 """, 

2185 } 

2186 make_files(d) 

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

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

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

2190 self.assert_made_files_unchanged(d) 

2191 

2192 def test_check_with_bad_checksum(self): 

2193 d = { 

2194 "bad.txt": """\ 

2195 //[[[cog 

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

2197 cog.outl("generated by cog") 

2198 cog.outl("blah blah.") 

2199 //]]] 

2200 This line was newly 

2201 generated by cog 

2202 blah blah. 

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

2204 """, 

2205 } 

2206 make_files(d) 

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

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

2209 self.assertEqual( 

2210 self.output.getvalue(), 

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

2212 ) 

2213 self.assert_made_files_unchanged(d) 

2214 

2215 

2216class WritabilityTests(TestCaseWithTempDir): 

2217 d = { 

2218 "test.cog": """\ 

2219 //[[[cog 

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

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

2222 //]]] 

2223 //[[[end]]] 

2224 """, 

2225 "test.out": """\ 

2226 //[[[cog 

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

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

2229 //]]] 

2230 void DoSomething(); 

2231 void DoAnotherThing(); 

2232 void DoLastThing(); 

2233 //[[[end]]] 

2234 """, 

2235 } 

2236 

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

2238 # for Windows 

2239 cmd_w_args = "attrib -R %s" 

2240 cmd_w_asterisk = "attrib -R *" 

2241 else: 

2242 # for unix-like 

2243 cmd_w_args = "chmod +w %s" 

2244 cmd_w_asterisk = "chmod +w *" 

2245 

2246 def setUp(self): 

2247 super().setUp() 

2248 make_files(self.d) 

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

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

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

2252 

2253 def tearDown(self): 

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

2255 super().tearDown() 

2256 

2257 def test_readonly_no_command(self): 

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

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

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

2261 

2262 def test_readonly_with_command(self): 

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

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

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

2266 

2267 def test_readonly_with_command_with_no_slot(self): 

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

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

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

2271 

2272 def test_readonly_with_ineffectual_command(self): 

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

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

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

2276 

2277 

2278class ChecksumTests(TestCaseWithTempDir): 

2279 def test_create_checksum_output(self): 

2280 d = { 

2281 "cog1.txt": """\ 

2282 //[[[cog 

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

2284 //]]] 

2285 This line was generated. 

2286 //[[[end]]] 

2287 This line was not. 

2288 """, 

2289 "cog1.out": """\ 

2290 //[[[cog 

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

2292 //]]] 

2293 This line was generated. 

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

2295 This line was not. 

2296 """, 

2297 } 

2298 

2299 make_files(d) 

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

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

2302 

2303 def test_check_checksum_output(self): 

2304 d = { 

2305 "cog1.txt": """\ 

2306 //[[[cog 

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

2308 cog.outl("generated by cog") 

2309 cog.outl("blah blah.") 

2310 //]]] 

2311 This line was generated. 

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

2313 """, 

2314 "cog1.out": """\ 

2315 //[[[cog 

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

2317 cog.outl("generated by cog") 

2318 cog.outl("blah blah.") 

2319 //]]] 

2320 This line was newly 

2321 generated by cog 

2322 blah blah. 

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

2324 """, 

2325 } 

2326 

2327 make_files(d) 

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

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

2330 

2331 def test_remove_checksum_output(self): 

2332 d = { 

2333 "cog1.txt": """\ 

2334 //[[[cog 

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

2336 cog.outl("generated by cog") 

2337 cog.outl("blah blah.") 

2338 //]]] 

2339 This line was generated. 

2340 //[[[end]]] (checksum: 8adb13fb59b996a1c7f0065ea9f3d893) fooey 

2341 """, 

2342 "cog1.out": """\ 

2343 //[[[cog 

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

2345 cog.outl("generated by cog") 

2346 cog.outl("blah blah.") 

2347 //]]] 

2348 This line was newly 

2349 generated by cog 

2350 blah blah. 

2351 //[[[end]]] fooey 

2352 """, 

2353 } 

2354 

2355 make_files(d) 

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

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

2358 

2359 def test_tampered_checksum_output(self): 

2360 d = { 

2361 "cog1.txt": """\ 

2362 //[[[cog 

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

2364 cog.outl("generated by cog") 

2365 cog.outl("blah blah.") 

2366 //]]] 

2367 Xhis line was newly 

2368 generated by cog 

2369 blah blah. 

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

2371 """, 

2372 "cog2.txt": """\ 

2373 //[[[cog 

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

2375 cog.outl("generated by cog") 

2376 cog.outl("blah blah.") 

2377 //]]] 

2378 This line was newly 

2379 generated by cog 

2380 blah blah! 

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

2382 """, 

2383 "cog3.txt": """\ 

2384 //[[[cog 

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

2386 cog.outl("generated by cog") 

2387 cog.outl("blah blah.") 

2388 //]]] 

2389 

2390 This line was newly 

2391 generated by cog 

2392 blah blah. 

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

2394 """, 

2395 "cog4.txt": """\ 

2396 //[[[cog 

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

2398 cog.outl("generated by cog") 

2399 cog.outl("blah blah.") 

2400 //]]] 

2401 This line was newly 

2402 generated by cog 

2403 blah blah.. 

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

2405 """, 

2406 "cog5.txt": """\ 

2407 //[[[cog 

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

2409 cog.outl("generated by cog") 

2410 cog.outl("blah blah.") 

2411 //]]] 

2412 This line was newly 

2413 generated by cog 

2414 blah blah. 

2415 extra 

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

2417 """, 

2418 "cog6.txt": """\ 

2419 //[[[cog 

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

2421 cog.outl("generated by cog") 

2422 cog.outl("blah blah.") 

2423 //]]] 

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

2425 """, 

2426 } 

2427 

2428 make_files(d) 

2429 with self.assertRaisesRegex( 

2430 CogError, 

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

2432 ): 

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

2434 with self.assertRaisesRegex( 

2435 CogError, 

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

2437 ): 

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

2439 with self.assertRaisesRegex( 

2440 CogError, 

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

2442 ): 

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

2444 with self.assertRaisesRegex( 

2445 CogError, 

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

2447 ): 

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

2449 with self.assertRaisesRegex( 

2450 CogError, 

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

2452 ): 

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

2454 with self.assertRaisesRegex( 

2455 CogError, 

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

2457 ): 

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

2459 

2460 def test_argv_isnt_modified(self): 

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

2462 orig_argv = argv[:] 

2463 self.cog.callable_main(argv) 

2464 self.assertEqual(argv, orig_argv) 

2465 

2466 

2467class CustomMarkerTests(TestCaseWithTempDir): 

2468 def test_customer_markers(self): 

2469 d = { 

2470 "test.cog": """\ 

2471 //{{ 

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

2473 //}} 

2474 //{{end}} 

2475 """, 

2476 "test.out": """\ 

2477 //{{ 

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

2479 //}} 

2480 void MyFunction(); 

2481 //{{end}} 

2482 """, 

2483 } 

2484 

2485 make_files(d) 

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

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

2488 

2489 def test_truly_wacky_markers(self): 

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

2491 d = { 

2492 "test.cog": """\ 

2493 //**( 

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

2495 //**) 

2496 //**(end)** 

2497 """, 

2498 "test.out": """\ 

2499 //**( 

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

2501 //**) 

2502 void MyFunction(); 

2503 //**(end)** 

2504 """, 

2505 } 

2506 

2507 make_files(d) 

2508 self.cog.callable_main( 

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

2510 ) 

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

2512 

2513 def test_change_just_one_marker(self): 

2514 d = { 

2515 "test.cog": """\ 

2516 //**( 

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

2518 //]]] 

2519 //[[[end]]] 

2520 """, 

2521 "test.out": """\ 

2522 //**( 

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

2524 //]]] 

2525 void MyFunction(); 

2526 //[[[end]]] 

2527 """, 

2528 } 

2529 

2530 make_files(d) 

2531 self.cog.callable_main( 

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

2533 ) 

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

2535 

2536 

2537class BlakeTests(TestCaseWithTempDir): 

2538 # Blake Winton's contributions. 

2539 def test_delete_code(self): 

2540 # -o sets the output file. 

2541 d = { 

2542 "test.cog": """\ 

2543 // This is my C++ file. 

2544 //[[[cog 

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

2546 for fn in fnames: 

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

2548 //]]] 

2549 Some Sample Code Here 

2550 //[[[end]]]Data Data 

2551 And Some More 

2552 """, 

2553 "test.out": """\ 

2554 // This is my C++ file. 

2555 void DoSomething(); 

2556 void DoAnotherThing(); 

2557 void DoLastThing(); 

2558 And Some More 

2559 """, 

2560 } 

2561 

2562 make_files(d) 

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

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

2565 

2566 def test_delete_code_with_dash_r_fails(self): 

2567 d = { 

2568 "test.cog": """\ 

2569 // This is my C++ file. 

2570 """ 

2571 } 

2572 

2573 make_files(d) 

2574 with self.assertRaisesRegex( 

2575 CogUsageError, 

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

2577 ): 

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

2579 

2580 def test_setting_globals(self): 

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

2582 # processFile(). 

2583 d = { 

2584 "test.cog": """\ 

2585 // This is my C++ file. 

2586 //[[[cog 

2587 for fn in fnames: 

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

2589 //]]] 

2590 Some Sample Code Here 

2591 //[[[end]]]""", 

2592 "test.out": """\ 

2593 // This is my C++ file. 

2594 void DoBlake(); 

2595 void DoWinton(); 

2596 void DoContribution(); 

2597 """, 

2598 } 

2599 

2600 make_files(d) 

2601 globals = {} 

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

2603 self.cog.options.delete_code = True 

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

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

2606 

2607 

2608class ErrorCallTests(TestCaseWithTempDir): 

2609 def test_error_call_has_no_traceback(self): 

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

2611 d = { 

2612 "error.cog": """\ 

2613 //[[[cog 

2614 cog.error("Something Bad!") 

2615 //]]] 

2616 //[[[end]]] 

2617 """, 

2618 } 

2619 

2620 make_files(d) 

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

2622 output = self.output.getvalue() 

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

2624 

2625 def test_real_error_has_traceback(self): 

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

2627 d = { 

2628 "error.cog": """\ 

2629 //[[[cog 

2630 raise RuntimeError("Hey!") 

2631 //]]] 

2632 //[[[end]]] 

2633 """, 

2634 } 

2635 

2636 make_files(d) 

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

2638 output = self.output.getvalue() 

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

2640 self.assertTrue( 

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

2642 ) 

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

2644 

2645 

2646# Things not yet tested: 

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