Coverage for cogapp/test_cogapp.py: 29.50%

854 statements  

« prev     ^ index     » next       coverage.py v7.5.0a1.dev1, created at 2024-04-15 15:50 -0400

1""" Test cogapp. 

2""" 

3 

4import io 

5import os 

6import os.path 

7import random 

8import re 

9import shutil 

10import stat 

11import sys 

12import tempfile 

13import threading 

14from unittest import TestCase 

15 

16from .cogapp import Cog, CogOptions, CogGenerator 

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

18from .cogapp import usage, __version__, main 

19from .makefiles import makeFiles 

20from .whiteutils import reindentBlock 

21 

22 

23class CogTestsInMemory(TestCase): 

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

25 """ 

26 

27 def testNoCog(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().processString(s), s) 

38 

39 def testSimple(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().processString(infile), outfile) 

67 

68 def testEmptyCog(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 = reindentBlock(infile) 

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

81 

82 def testMultipleCogs(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 = reindentBlock(infile) 

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

105 

106 def testTrimBlankLines(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 = reindentBlock(infile) 

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

123 

124 def testTrimEmptyBlankLines(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 = reindentBlock(infile) 

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

142 

143 def testTrimBlankLinesWithLastPartial(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 = reindentBlock(infile) 

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

157 

158 def testCogOutDedent(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 = reindentBlock(infile) 

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

189 

190 def test22EndOfLine(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 = reindentBlock(infile) 

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

207 

208 def testIndentedCode(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 = reindentBlock(infile) 

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

225 

226 def testPrefixedCode(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 = reindentBlock(infile) 

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

241 

242 def testPrefixedIndentedCode(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 = reindentBlock(infile) 

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

258 

259 def testBogusPrefixMatch(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 = reindentBlock(infile) 

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

276 

277 def testNoFinalNewline(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 = reindentBlock(infile) 

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

294 

295 def testNoOutputAtAll(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 = reindentBlock(infile) 

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

308 

309 def testPurelyBlankLine(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 = reindentBlock(infile.replace('$', '')) 

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

328 

329 def testEmptyOutl(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 = reindentBlock(infile) 

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

354 

355 def testFirstLineNum(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 = reindentBlock(infile) 

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

373 

374 def testCompactOneLineCode(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 = reindentBlock(infile) 

392 self.assertEqual(Cog().processString(infile), reindentBlock(outfile)) 

393 

394 def testInsideOutCompact(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(CogError, r"^infile.txt\(2\): Cog code markers inverted$"): 

403 Cog().processString(reindentBlock(infile), "infile.txt") 

404 

405 def testSharingGlobals(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 = reindentBlock(infile) 

428 self.assertEqual(Cog().processString(infile), reindentBlock(outfile)) 

429 

430 def testAssertInCogCode(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 = reindentBlock(infile) 

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

440 Cog().processString(infile) 

441 

442 def testCogPrevious(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 = reindentBlock(infile) 

466 self.assertEqual(Cog().processString(infile), reindentBlock(outfile)) 

467 

468 

469class CogOptionsTests(TestCase): 

470 """ Test the CogOptions class. 

471 """ 

472 

473 def testEquality(self): 

474 o = CogOptions() 

475 p = CogOptions() 

476 self.assertEqual(o, p) 

477 o.parseArgs(['-r']) 

478 self.assertNotEqual(o, p) 

479 p.parseArgs(['-r']) 

480 self.assertEqual(o, p) 

481 

482 def testCloning(self): 

483 o = CogOptions() 

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

485 p = o.clone() 

486 self.assertEqual(o, p) 

487 p.parseArgs(['-I', 'huey', '-D', 'foo=quux']) 

488 self.assertNotEqual(o, p) 

489 q = CogOptions() 

490 q.parseArgs(['-I', 'fooey', '-I', 'booey', '-s', ' /*x*/', '-I', 'huey', '-D', 'foo=quux']) 

491 self.assertEqual(p, q) 

492 

493 def testCombiningFlags(self): 

494 # Single-character flags can be combined. 

495 o = CogOptions() 

496 o.parseArgs(['-e', '-r', '-z']) 

497 p = CogOptions() 

498 p.parseArgs(['-erz']) 

499 self.assertEqual(o, p) 

500 

501 def testMarkers(self): 

502 o = CogOptions() 

503 o._parse_markers('a b c') 

504 self.assertEqual('a', o.sBeginSpec) 

505 self.assertEqual('b', o.sEndSpec) 

506 self.assertEqual('c', o.sEndOutput) 

507 

508 def testMarkersSwitch(self): 

509 o = CogOptions() 

510 o.parseArgs(['--markers', 'a b c']) 

511 self.assertEqual('a', o.sBeginSpec) 

512 self.assertEqual('b', o.sEndSpec) 

513 self.assertEqual('c', o.sEndOutput) 

514 

515 

516class FileStructureTests(TestCase): 

517 """ Test cases to check that we're properly strict about the structure 

518 of files. 

519 """ 

520 

521 def isBad(self, infile, msg=None): 

522 infile = reindentBlock(infile) 

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

524 Cog().processString(infile, 'infile.txt') 

525 

526 def testBeginNoEnd(self): 

527 infile = """\ 

528 Fooey 

529 #[[[cog 

530 cog.outl('hello') 

531 """ 

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

533 

534 def testNoEoo(self): 

535 infile = """\ 

536 Fooey 

537 #[[[cog 

538 cog.outl('hello') 

539 #]]] 

540 """ 

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

542 

543 infile2 = """\ 

544 Fooey 

545 #[[[cog 

546 cog.outl('hello') 

547 #]]] 

548 #[[[cog 

549 cog.outl('goodbye') 

550 #]]] 

551 """ 

552 self.isBad(infile2, "infile.txt(5): Unexpected '[[[cog'") 

553 

554 def testStartWithEnd(self): 

555 infile = """\ 

556 #]]] 

557 """ 

558 self.isBad(infile, "infile.txt(1): Unexpected ']]]'") 

559 

560 infile2 = """\ 

561 #[[[cog 

562 cog.outl('hello') 

563 #]]] 

564 #[[[end]]] 

565 #]]] 

566 """ 

567 self.isBad(infile2, "infile.txt(5): Unexpected ']]]'") 

568 

569 def testStartWithEoo(self): 

570 infile = """\ 

571 #[[[end]]] 

572 """ 

573 self.isBad(infile, "infile.txt(1): Unexpected '[[[end]]]'") 

574 

575 infile2 = """\ 

576 #[[[cog 

577 cog.outl('hello') 

578 #]]] 

579 #[[[end]]] 

580 #[[[end]]] 

581 """ 

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

583 

584 def testNoEnd(self): 

585 infile = """\ 

586 #[[[cog 

587 cog.outl("hello") 

588 #[[[end]]] 

589 """ 

590 self.isBad(infile, "infile.txt(3): Unexpected '[[[end]]]'") 

591 

592 infile2 = """\ 

593 #[[[cog 

594 cog.outl('hello') 

595 #]]] 

596 #[[[end]]] 

597 #[[[cog 

598 cog.outl("hello") 

599 #[[[end]]] 

600 """ 

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

602 

603 def testTwoBegins(self): 

604 infile = """\ 

605 #[[[cog 

606 #[[[cog 

607 cog.outl("hello") 

608 #]]] 

609 #[[[end]]] 

610 """ 

611 self.isBad(infile, "infile.txt(2): Unexpected '[[[cog'") 

612 

613 infile2 = """\ 

614 #[[[cog 

615 cog.outl("hello") 

616 #]]] 

617 #[[[end]]] 

618 #[[[cog 

619 #[[[cog 

620 cog.outl("hello") 

621 #]]] 

622 #[[[end]]] 

623 """ 

624 self.isBad(infile2, "infile.txt(6): Unexpected '[[[cog'") 

625 

626 def testTwoEnds(self): 

627 infile = """\ 

628 #[[[cog 

629 cog.outl("hello") 

630 #]]] 

631 #]]] 

632 #[[[end]]] 

633 """ 

634 self.isBad(infile, "infile.txt(4): Unexpected ']]]'") 

635 

636 infile2 = """\ 

637 #[[[cog 

638 cog.outl("hello") 

639 #]]] 

640 #[[[end]]] 

641 #[[[cog 

642 cog.outl("hello") 

643 #]]] 

644 #]]] 

645 #[[[end]]] 

646 """ 

647 self.isBad(infile2, "infile.txt(8): Unexpected ']]]'") 

648 

649 

650class CogErrorTests(TestCase): 

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

652 """ 

653 

654 def testErrorMsg(self): 

655 infile = """\ 

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

657 [[[end]]] 

658 """ 

659 

660 infile = reindentBlock(infile) 

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

662 Cog().processString(infile) 

663 

664 def testErrorNoMsg(self): 

665 infile = """\ 

666 [[[cog cog.error()]]] 

667 [[[end]]] 

668 """ 

669 

670 infile = reindentBlock(infile) 

671 with self.assertRaisesRegex(CogGeneratedError, "^Error raised by cog generator.$"): 

672 Cog().processString(infile) 

673 

674 def testNoErrorIfErrorNotCalled(self): 

675 infile = """\ 

676 --[[[cog 

677 --import cog 

678 --for i in range(3): 

679 -- if i > 10: 

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

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

682 --]]] 

683 xx0 

684 xx1 

685 xx2 

686 --[[[end]]] 

687 """ 

688 

689 infile = reindentBlock(infile) 

690 self.assertEqual(Cog().processString(infile), infile) 

691 

692 

693class CogGeneratorGetCodeTests(TestCase): 

694 """ Unit tests against CogGenerator to see if its getCode() method works 

695 properly. 

696 """ 

697 

698 def setUp(self): 

699 """ All tests get a generator to use, and short same-length names for 

700 the functions we're going to use. 

701 """ 

702 self.gen = CogGenerator() 

703 self.m = self.gen.parseMarker 

704 self.l = self.gen.parseLine 

705 

706 def testEmpty(self): 

707 self.m('// [[[cog') 

708 self.m('// ]]]') 

709 self.assertEqual(self.gen.getCode(), '') 

710 

711 def testSimple(self): 

712 self.m('// [[[cog') 

713 self.l(' print "hello"') 

714 self.l(' print "bye"') 

715 self.m('// ]]]') 

716 self.assertEqual(self.gen.getCode(), 'print "hello"\nprint "bye"') 

717 

718 def testCompressed1(self): 

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

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

721 self.l('// hello') 

722 self.l('// bye') 

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

724 self.assertEqual(self.gen.getCode(), 'hello\nbye') 

725 

726 def testCompressed2(self): 

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

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

729 self.l('hello') 

730 self.l('bye') 

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

732 self.assertEqual(self.gen.getCode(), 'hello\nbye') 

733 

734 def testCompressed3(self): 

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

736 self.m('// [[[cog') 

737 self.l('print """hello') 

738 self.l('bye') 

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

740 self.assertEqual(self.gen.getCode(), 'print """hello\nbye') 

741 

742 def testCompressed4(self): 

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

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

745 self.l('hello') 

746 self.l('bye""")') 

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

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

749 

750 def testNoCommonPrefixForMarkers(self): 

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

752 # C++ compiler. 

753 self.m('#if 0 //[[[cog') 

754 self.l('\timport cog, sys') 

755 self.l('') 

756 self.l('\tprint sys.argv') 

757 self.m('#endif //]]]') 

758 self.assertEqual(self.gen.getCode(), 'import cog, sys\n\nprint sys.argv') 

759 

760 

761class TestCaseWithTempDir(TestCase): 

762 

763 def newCog(self): 

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

765 """ 

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

767 self.cog = Cog() 

768 self.output = io.StringIO() 

769 self.cog.setOutput(stdout=self.output, stderr=self.output) 

770 

771 def setUp(self): 

772 # Create a temporary directory. 

773 self.tempdir = os.path.join(tempfile.gettempdir(), 'testcog_tempdir_' + str(random.random())[2:]) 

774 os.mkdir(self.tempdir) 

775 self.olddir = os.getcwd() 

776 os.chdir(self.tempdir) 

777 self.newCog() 

778 

779 def tearDown(self): 

780 os.chdir(self.olddir) 

781 # Get rid of the temporary directory. 

782 shutil.rmtree(self.tempdir) 

783 

784 def assertFilesSame(self, sFName1, sFName2): 

785 with open(os.path.join(self.tempdir, sFName1), 'rb') as f1: 

786 text1 = f1.read() 

787 with open(os.path.join(self.tempdir, sFName2), 'rb') as f2: 

788 text2 = f2.read() 

789 self.assertEqual(text1, text2) 

790 

791 def assertFileContent(self, fname, content): 

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

793 with open(absname, 'rb') as f: 

794 file_content = f.read() 

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

796 

797 

798class ArgumentHandlingTests(TestCaseWithTempDir): 

799 

800 def testArgumentFailure(self): 

801 # Return value 2 means usage problem. 

802 self.assertEqual(self.cog.main(['argv0', '-j']), 2) 

803 output = self.output.getvalue() 

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

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

806 self.cog.callableMain(['argv0']) 

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

808 self.cog.callableMain(['argv0', '-j']) 

809 

810 def testNoDashOAndAtFile(self): 

811 makeFiles({"cogfiles.txt": "# Please run cog"}) 

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

813 self.cog.callableMain(['argv0', '-o', 'foo', '@cogfiles.txt']) 

814 

815 def testNoDashOAndAmpFile(self): 

816 makeFiles({"cogfiles.txt": "# Please run cog"}) 

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

818 self.cog.callableMain(['argv0', '-o', 'foo', '&cogfiles.txt']) 

819 

820 def testDashV(self): 

821 self.assertEqual(self.cog.main(['argv0', '-v']), 0) 

822 output = self.output.getvalue() 

823 self.assertEqual('Cog version %s\n' % __version__, output) 

824 

825 def producesHelp(self, args): 

826 self.newCog() 

827 argv = ['argv0'] + args.split() 

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

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

830 

831 def testDashH(self): 

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

833 self.producesHelp("-h") 

834 self.producesHelp("-?") 

835 self.producesHelp("fooey.txt -h") 

836 self.producesHelp("-o -r @fooey.txt -? @booey.txt") 

837 

838 def testDashOAndDashR(self): 

839 d = { 

840 'cogfile.txt': """\ 

841 # Please run cog 

842 """ 

843 } 

844 

845 makeFiles(d) 

846 with self.assertRaisesRegex(CogUsageError, r"^Can't use -o with -r \(they are opposites\)$"): 

847 self.cog.callableMain(['argv0', '-o', 'foo', '-r', 'cogfile.txt']) 

848 

849 def testDashZ(self): 

850 d = { 

851 'test.cog': """\ 

852 // This is my C++ file. 

853 //[[[cog 

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

855 for fn in fnames: 

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

857 //]]] 

858 """, 

859 

860 'test.out': """\ 

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 void DoSomething(); 

868 void DoAnotherThing(); 

869 void DoLastThing(); 

870 """, 

871 } 

872 

873 makeFiles(d) 

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

875 self.cog.callableMain(['argv0', '-r', 'test.cog']) 

876 self.newCog() 

877 self.cog.callableMain(['argv0', '-r', '-z', 'test.cog']) 

878 self.assertFilesSame('test.cog', 'test.out') 

879 

880 def testBadDashD(self): 

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

882 self.cog.callableMain(['argv0', '-Dfooey', 'cog.txt']) 

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

884 self.cog.callableMain(['argv0', '-D', 'fooey', 'cog.txt']) 

885 

886 def testBadMarkers(self): 

887 with self.assertRaisesRegex(CogUsageError, r"^--markers requires 3 values separated by spaces, could not parse 'X'$"): 

888 self.cog.callableMain(['argv0', '--markers=X']) 

889 with self.assertRaisesRegex(CogUsageError, r"^--markers requires 3 values separated by spaces, could not parse 'A B C D'$"): 

890 self.cog.callableMain(['argv0', '--markers=A B C D']) 

891 

892 

893class TestMain(TestCaseWithTempDir): 

894 def setUp(self): 

895 super().setUp() 

896 self.old_argv = sys.argv[:] 

897 self.old_stderr = sys.stderr 

898 sys.stderr = io.StringIO() 

899 

900 def tearDown(self): 

901 sys.stderr = self.old_stderr 

902 sys.argv = self.old_argv 

903 sys.modules.pop('mycode', None) 

904 super().tearDown() 

905 

906 def test_main_function(self): 

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

908 ret = main() 

909 self.assertEqual(ret, 2) 

910 stderr = sys.stderr.getvalue() 

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

912 

913 files = { 

914 'test.cog': """\ 

915 //[[[cog 

916 def func(): 

917 import mycode 

918 mycode.boom() 

919 //]]] 

920 //[[[end]]] 

921 ----- 

922 //[[[cog 

923 func() 

924 //]]] 

925 //[[[end]]] 

926 """, 

927 

928 'mycode.py': """\ 

929 def boom(): 

930 [][0] 

931 """, 

932 } 

933 

934 def test_error_report(self): 

935 self.check_error_report() 

936 

937 def test_error_report_with_prologue(self): 

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

939 

940 def check_error_report(self, *args): 

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

942 makeFiles(self.files) 

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

944 main() 

945 expected = reindentBlock("""\ 

946 Traceback (most recent call last): 

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

948 func() 

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

950 mycode.boom() 

951 File "MYCODE", line 2, in boom 

952 [][0] 

953 IndexError: list index out of range 

954 """) 

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

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

957 

958 def test_error_in_prologue(self): 

959 makeFiles(self.files) 

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

961 main() 

962 expected = reindentBlock("""\ 

963 Traceback (most recent call last): 

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

965 import mycode; 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 

974 

975class TestFileHandling(TestCaseWithTempDir): 

976 

977 def testSimple(self): 

978 d = { 

979 'test.cog': """\ 

980 // This is my C++ file. 

981 //[[[cog 

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

983 for fn in fnames: 

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

985 //]]] 

986 //[[[end]]] 

987 """, 

988 

989 'test.out': """\ 

990 // This is my C++ file. 

991 //[[[cog 

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

993 for fn in fnames: 

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

995 //]]] 

996 void DoSomething(); 

997 void DoAnotherThing(); 

998 void DoLastThing(); 

999 //[[[end]]] 

1000 """, 

1001 } 

1002 

1003 makeFiles(d) 

1004 self.cog.callableMain(['argv0', '-r', 'test.cog']) 

1005 self.assertFilesSame('test.cog', 'test.out') 

1006 output = self.output.getvalue() 

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

1008 

1009 def testPrintOutput(self): 

1010 d = { 

1011 'test.cog': """\ 

1012 // This is my C++ file. 

1013 //[[[cog 

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

1015 for fn in fnames: 

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

1017 //]]] 

1018 //[[[end]]] 

1019 """, 

1020 

1021 'test.out': """\ 

1022 // This is my C++ file. 

1023 //[[[cog 

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

1025 for fn in fnames: 

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

1027 //]]] 

1028 void DoSomething(); 

1029 void DoAnotherThing(); 

1030 void DoLastThing(); 

1031 //[[[end]]] 

1032 """, 

1033 } 

1034 

1035 makeFiles(d) 

1036 self.cog.callableMain(['argv0', '-rP', 'test.cog']) 

1037 self.assertFilesSame('test.cog', 'test.out') 

1038 output = self.output.getvalue() 

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

1040 

1041 def testWildcards(self): 

1042 d = { 

1043 'test.cog': """\ 

1044 // This is my C++ file. 

1045 //[[[cog 

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

1047 for fn in fnames: 

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

1049 //]]] 

1050 //[[[end]]] 

1051 """, 

1052 

1053 'test2.cog': """\ 

1054 // This is my C++ file. 

1055 //[[[cog 

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

1057 for fn in fnames: 

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

1059 //]]] 

1060 //[[[end]]] 

1061 """, 

1062 

1063 'test.out': """\ 

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 void DoSomething(); 

1071 void DoAnotherThing(); 

1072 void DoLastThing(); 

1073 //[[[end]]] 

1074 """, 

1075 

1076 'not_this_one.cog': """\ 

1077 // This is my C++ file. 

1078 //[[[cog 

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

1080 for fn in fnames: 

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

1082 //]]] 

1083 //[[[end]]] 

1084 """, 

1085 

1086 'not_this_one.out': """\ 

1087 // This is my C++ file. 

1088 //[[[cog 

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

1090 for fn in fnames: 

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

1092 //]]] 

1093 //[[[end]]] 

1094 """, 

1095 } 

1096 

1097 makeFiles(d) 

1098 self.cog.callableMain(['argv0', '-r', 't*.cog']) 

1099 self.assertFilesSame('test.cog', 'test.out') 

1100 self.assertFilesSame('test2.cog', 'test.out') 

1101 self.assertFilesSame('not_this_one.cog', 'not_this_one.out') 

1102 output = self.output.getvalue() 

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

1104 

1105 def testOutputFile(self): 

1106 # -o sets the output file. 

1107 d = { 

1108 'test.cog': """\ 

1109 // This is my C++ file. 

1110 //[[[cog 

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

1112 for fn in fnames: 

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

1114 //]]] 

1115 //[[[end]]] 

1116 """, 

1117 

1118 'test.out': """\ 

1119 // This is my C++ file. 

1120 //[[[cog 

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

1122 for fn in fnames: 

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

1124 //]]] 

1125 void DoSomething(); 

1126 void DoAnotherThing(); 

1127 void DoLastThing(); 

1128 //[[[end]]] 

1129 """, 

1130 } 

1131 

1132 makeFiles(d) 

1133 self.cog.callableMain(['argv0', '-o', 'in/a/dir/test.cogged', 'test.cog']) 

1134 self.assertFilesSame('in/a/dir/test.cogged', 'test.out') 

1135 

1136 def testAtFile(self): 

1137 d = { 

1138 'one.cog': """\ 

1139 //[[[cog 

1140 cog.outl("hello world") 

1141 //]]] 

1142 //[[[end]]] 

1143 """, 

1144 

1145 'one.out': """\ 

1146 //[[[cog 

1147 cog.outl("hello world") 

1148 //]]] 

1149 hello world 

1150 //[[[end]]] 

1151 """, 

1152 

1153 'two.cog': """\ 

1154 //[[[cog 

1155 cog.outl("goodbye cruel world") 

1156 //]]] 

1157 //[[[end]]] 

1158 """, 

1159 

1160 'two.out': """\ 

1161 //[[[cog 

1162 cog.outl("goodbye cruel world") 

1163 //]]] 

1164 goodbye cruel world 

1165 //[[[end]]] 

1166 """, 

1167 

1168 'cogfiles.txt': """\ 

1169 # Please run cog 

1170 one.cog 

1171 

1172 two.cog 

1173 """ 

1174 } 

1175 

1176 makeFiles(d) 

1177 self.cog.callableMain(['argv0', '-r', '@cogfiles.txt']) 

1178 self.assertFilesSame('one.cog', 'one.out') 

1179 self.assertFilesSame('two.cog', 'two.out') 

1180 output = self.output.getvalue() 

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

1182 

1183 def testNestedAtFile(self): 

1184 d = { 

1185 'one.cog': """\ 

1186 //[[[cog 

1187 cog.outl("hello world") 

1188 //]]] 

1189 //[[[end]]] 

1190 """, 

1191 

1192 'one.out': """\ 

1193 //[[[cog 

1194 cog.outl("hello world") 

1195 //]]] 

1196 hello world 

1197 //[[[end]]] 

1198 """, 

1199 

1200 'two.cog': """\ 

1201 //[[[cog 

1202 cog.outl("goodbye cruel world") 

1203 //]]] 

1204 //[[[end]]] 

1205 """, 

1206 

1207 'two.out': """\ 

1208 //[[[cog 

1209 cog.outl("goodbye cruel world") 

1210 //]]] 

1211 goodbye cruel world 

1212 //[[[end]]] 

1213 """, 

1214 

1215 'cogfiles.txt': """\ 

1216 # Please run cog 

1217 one.cog 

1218 @cogfiles2.txt 

1219 """, 

1220 

1221 'cogfiles2.txt': """\ 

1222 # This one too, please. 

1223 two.cog 

1224 """, 

1225 } 

1226 

1227 makeFiles(d) 

1228 self.cog.callableMain(['argv0', '-r', '@cogfiles.txt']) 

1229 self.assertFilesSame('one.cog', 'one.out') 

1230 self.assertFilesSame('two.cog', 'two.out') 

1231 output = self.output.getvalue() 

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

1233 

1234 def testAtFileWithArgs(self): 

1235 d = { 

1236 'both.cog': """\ 

1237 //[[[cog 

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

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

1240 //]]] 

1241 //[[[end]]] 

1242 """, 

1243 

1244 'one.out': """\ 

1245 //[[[cog 

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

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

1248 //]]] 

1249 one: True // ONE 

1250 two: False // ONE 

1251 //[[[end]]] 

1252 """, 

1253 

1254 'two.out': """\ 

1255 //[[[cog 

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

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

1258 //]]] 

1259 one: False // TWO 

1260 two: True // TWO 

1261 //[[[end]]] 

1262 """, 

1263 

1264 'cogfiles.txt': """\ 

1265 # Please run cog 

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

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

1268 """ 

1269 } 

1270 

1271 makeFiles(d) 

1272 self.cog.callableMain(['argv0', '@cogfiles.txt']) 

1273 self.assertFilesSame('in/a/dir/both.one', 'one.out') 

1274 self.assertFilesSame('in/a/dir/both.two', 'two.out') 

1275 

1276 def testAtFileWithBadArgCombo(self): 

1277 d = { 

1278 'both.cog': """\ 

1279 //[[[cog 

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

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

1282 //]]] 

1283 //[[[end]]] 

1284 """, 

1285 

1286 'cogfiles.txt': """\ 

1287 # Please run cog 

1288 both.cog 

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

1290 """ 

1291 } 

1292 

1293 makeFiles(d) 

1294 with self.assertRaisesRegex(CogUsageError, r"^Can't use -d with -r \(or you would delete all your source!\)$"): 

1295 self.cog.callableMain(['argv0', '-r', '@cogfiles.txt']) 

1296 

1297 def testAtFileWithTrickyFilenames(self): 

1298 def fix_backslashes(files_txt): 

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

1300 if sys.platform != "win32": 

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

1302 return files_txt 

1303 

1304 d = { 

1305 'one 1.cog': """\ 

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

1307 """, 

1308 

1309 'one.out': """\ 

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

1311 hello world //xxx 

1312 """, 

1313 

1314 'subdir': { 

1315 'subback.cog': """\ 

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

1317 """, 

1318 

1319 'subfwd.cog': """\ 

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

1321 """, 

1322 }, 

1323 

1324 'subback.out': """\ 

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

1326 down deep with backslashes //yyy 

1327 """, 

1328 

1329 'subfwd.out': """\ 

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

1331 down deep with slashes //zzz 

1332 """, 

1333 

1334 'cogfiles.txt': fix_backslashes("""\ 

1335 # Please run cog 

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

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

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

1339 """) 

1340 } 

1341 

1342 makeFiles(d) 

1343 self.cog.callableMain(['argv0', '-z', '-r', '@cogfiles.txt']) 

1344 self.assertFilesSame('one 1.cog', 'one.out') 

1345 self.assertFilesSame('subdir/subback.cog', 'subback.out') 

1346 self.assertFilesSame('subdir/subfwd.cog', 'subfwd.out') 

1347 

1348 def testAmpFile(self): 

1349 d = { 

1350 'code': { 

1351 'files_to_cog': """\ 

1352 # A locally resolved file name. 

1353 test.cog 

1354 """, 

1355 

1356 'test.cog': """\ 

1357 //[[[cog 

1358 import myampsubmodule 

1359 //]]] 

1360 //[[[end]]] 

1361 """, 

1362 

1363 'test.out': """\ 

1364 //[[[cog 

1365 import myampsubmodule 

1366 //]]] 

1367 Hello from myampsubmodule 

1368 //[[[end]]] 

1369 """, 

1370 

1371 'myampsubmodule.py': """\ 

1372 import cog 

1373 cog.outl("Hello from myampsubmodule") 

1374 """ 

1375 } 

1376 } 

1377 

1378 makeFiles(d) 

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

1380 self.cog.callableMain(['argv0', '-r', '&code/files_to_cog']) 

1381 self.assertFilesSame('code/test.cog', 'code/test.out') 

1382 

1383 def run_with_verbosity(self, verbosity): 

1384 d = { 

1385 'unchanged.cog': """\ 

1386 //[[[cog 

1387 cog.outl("hello world") 

1388 //]]] 

1389 hello world 

1390 //[[[end]]] 

1391 """, 

1392 

1393 'changed.cog': """\ 

1394 //[[[cog 

1395 cog.outl("goodbye cruel world") 

1396 //]]] 

1397 //[[[end]]] 

1398 """, 

1399 

1400 'cogfiles.txt': """\ 

1401 unchanged.cog 

1402 changed.cog 

1403 """ 

1404 } 

1405 

1406 makeFiles(d) 

1407 self.cog.callableMain(['argv0', '-r', '--verbosity='+verbosity, '@cogfiles.txt']) 

1408 output = self.output.getvalue() 

1409 return output 

1410 

1411 def test_verbosity0(self): 

1412 output = self.run_with_verbosity("0") 

1413 self.assertEqual(output, "") 

1414 

1415 def test_verbosity1(self): 

1416 output = self.run_with_verbosity("1") 

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

1418 

1419 def test_verbosity2(self): 

1420 output = self.run_with_verbosity("2") 

1421 self.assertEqual(output, "Cogging unchanged.cog\nCogging changed.cog (changed)\n") 

1422 

1423 

1424class CogTestLineEndings(TestCaseWithTempDir): 

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

1426 

1427 lines_in = ['Some text.', 

1428 '//[[[cog', 

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

1430 '//]]]', 

1431 'gobbledegook.', 

1432 '//[[[end]]]', 

1433 'epilogue.', 

1434 ''] 

1435 

1436 lines_out = ['Some text.', 

1437 '//[[[cog', 

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

1439 '//]]]', 

1440 'Cog text', 

1441 '//[[[end]]]', 

1442 'epilogue.', 

1443 ''] 

1444 

1445 def testOutputNativeEol(self): 

1446 makeFiles({'infile': '\n'.join(self.lines_in)}) 

1447 self.cog.callableMain(['argv0', '-o', 'outfile', 'infile']) 

1448 self.assertFileContent('outfile', os.linesep.join(self.lines_out)) 

1449 

1450 def testOutputLfEol(self): 

1451 makeFiles({'infile': '\n'.join(self.lines_in)}) 

1452 self.cog.callableMain(['argv0', '-U', '-o', 'outfile', 'infile']) 

1453 self.assertFileContent('outfile', '\n'.join(self.lines_out)) 

1454 

1455 def testReplaceNativeEol(self): 

1456 makeFiles({'test.cog': '\n'.join(self.lines_in)}) 

1457 self.cog.callableMain(['argv0', '-r', 'test.cog']) 

1458 self.assertFileContent('test.cog', os.linesep.join(self.lines_out)) 

1459 

1460 def testReplaceLfEol(self): 

1461 makeFiles({'test.cog': '\n'.join(self.lines_in)}) 

1462 self.cog.callableMain(['argv0', '-U', '-r', 'test.cog']) 

1463 self.assertFileContent('test.cog', '\n'.join(self.lines_out)) 

1464 

1465 

1466class CogTestCharacterEncoding(TestCaseWithTempDir): 

1467 

1468 def testSimple(self): 

1469 d = { 

1470 'test.cog': b"""\ 

1471 // This is my C++ file. 

1472 //[[[cog 

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

1474 //]]] 

1475 //[[[end]]] 

1476 """, 

1477 

1478 'test.out': b"""\ 

1479 // This is my C++ file. 

1480 //[[[cog 

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

1482 //]]] 

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

1484 //[[[end]]] 

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

1486 } 

1487 

1488 makeFiles(d) 

1489 self.cog.callableMain(['argv0', '-r', 'test.cog']) 

1490 self.assertFilesSame('test.cog', 'test.out') 

1491 output = self.output.getvalue() 

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

1493 

1494 def testFileEncodingOption(self): 

1495 d = { 

1496 'test.cog': b"""\ 

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

1498 //[[[cog 

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

1500 //]]] 

1501 //[[[end]]] 

1502 """, 

1503 

1504 'test.out': b"""\ 

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

1506 //[[[cog 

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

1508 //]]] 

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

1510 //[[[end]]] 

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

1512 } 

1513 

1514 makeFiles(d) 

1515 self.cog.callableMain(['argv0', '-n', 'cp1251', '-r', 'test.cog']) 

1516 self.assertFilesSame('test.cog', 'test.out') 

1517 output = self.output.getvalue() 

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

1519 

1520 

1521class TestCaseWithImports(TestCaseWithTempDir): 

1522 """ When running tests which import modules, the sys.modules list 

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

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

1525 """ 

1526 

1527 def setUp(self): 

1528 super().setUp() 

1529 self.sysmodulekeys = list(sys.modules) 

1530 

1531 def tearDown(self): 

1532 modstoscrub = [ 

1533 modname 

1534 for modname in sys.modules 

1535 if modname not in self.sysmodulekeys 

1536 ] 

1537 for modname in modstoscrub: 

1538 del sys.modules[modname] 

1539 super().tearDown() 

1540 

1541 

1542class CogIncludeTests(TestCaseWithImports): 

1543 dincludes = { 

1544 'test.cog': """\ 

1545 //[[[cog 

1546 import mymodule 

1547 //]]] 

1548 //[[[end]]] 

1549 """, 

1550 

1551 'test.out': """\ 

1552 //[[[cog 

1553 import mymodule 

1554 //]]] 

1555 Hello from mymodule 

1556 //[[[end]]] 

1557 """, 

1558 

1559 'test2.out': """\ 

1560 //[[[cog 

1561 import mymodule 

1562 //]]] 

1563 Hello from mymodule in inc2 

1564 //[[[end]]] 

1565 """, 

1566 

1567 'include': { 

1568 'mymodule.py': """\ 

1569 import cog 

1570 cog.outl("Hello from mymodule") 

1571 """ 

1572 }, 

1573 

1574 'inc2': { 

1575 'mymodule.py': """\ 

1576 import cog 

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

1578 """ 

1579 }, 

1580 

1581 'inc3': { 

1582 'someothermodule.py': """\ 

1583 import cog 

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

1585 """ 

1586 }, 

1587 } 

1588 

1589 def testNeedIncludePath(self): 

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

1591 makeFiles(self.dincludes) 

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

1593 with self.assertRaisesRegex(CogUserException, msg): 

1594 self.cog.callableMain(['argv0', '-r', 'test.cog']) 

1595 

1596 def testIncludePath(self): 

1597 # Test that -I adds include directories properly. 

1598 makeFiles(self.dincludes) 

1599 self.cog.callableMain(['argv0', '-r', '-I', 'include', 'test.cog']) 

1600 self.assertFilesSame('test.cog', 'test.out') 

1601 

1602 def testTwoIncludePaths(self): 

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

1604 makeFiles(self.dincludes) 

1605 self.cog.callableMain(['argv0', '-r', '-I', 'include', '-I', 'inc2', 'test.cog']) 

1606 self.assertFilesSame('test.cog', 'test.out') 

1607 

1608 def testTwoIncludePaths2(self): 

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

1610 makeFiles(self.dincludes) 

1611 self.cog.callableMain(['argv0', '-r', '-I', 'inc2', '-I', 'include', 'test.cog']) 

1612 self.assertFilesSame('test.cog', 'test2.out') 

1613 

1614 def testUselessIncludePath(self): 

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

1616 makeFiles(self.dincludes) 

1617 self.cog.callableMain(['argv0', '-r', '-I', 'inc3', '-I', 'include', 'test.cog']) 

1618 self.assertFilesSame('test.cog', 'test.out') 

1619 

1620 def testSysPathIsUnchanged(self): 

1621 d = { 

1622 'bad.cog': """\ 

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

1624 //[[[end]]] 

1625 """, 

1626 'good.cog': """\ 

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

1628 //[[[end]]] 

1629 """, 

1630 } 

1631 

1632 makeFiles(d) 

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

1634 oldsyspath = sys.path[:] 

1635 self.newCog() 

1636 self.assertEqual(oldsyspath, sys.path) 

1637 # Is it unchanged for a successful run? 

1638 self.newCog() 

1639 self.cog.callableMain(['argv0', '-r', 'good.cog']) 

1640 self.assertEqual(oldsyspath, sys.path) 

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

1642 self.newCog() 

1643 self.cog.callableMain(['argv0', '-r', '-I', 'xyzzy', 'good.cog']) 

1644 self.assertEqual(oldsyspath, sys.path) 

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

1646 self.newCog() 

1647 self.cog.callableMain(['argv0', '-r', '-I', 'xyzzy', '-I', 'quux', 'good.cog']) 

1648 self.assertEqual(oldsyspath, sys.path) 

1649 # Is it unchanged for a failed run? 

1650 self.newCog() 

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

1652 self.cog.callableMain(['argv0', '-r', 'bad.cog']) 

1653 self.assertEqual(oldsyspath, sys.path) 

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

1655 self.newCog() 

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

1657 self.cog.callableMain(['argv0', '-r', '-I', 'xyzzy', 'bad.cog']) 

1658 self.assertEqual(oldsyspath, sys.path) 

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

1660 self.newCog() 

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

1662 self.cog.callableMain(['argv0', '-r', '-I', 'xyzzy', '-I', 'quux', 'bad.cog']) 

1663 self.assertEqual(oldsyspath, sys.path) 

1664 

1665 def testSubDirectories(self): 

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

1667 

1668 d = { 

1669 'code': { 

1670 'test.cog': """\ 

1671 //[[[cog 

1672 import mysubmodule 

1673 //]]] 

1674 //[[[end]]] 

1675 """, 

1676 

1677 'test.out': """\ 

1678 //[[[cog 

1679 import mysubmodule 

1680 //]]] 

1681 Hello from mysubmodule 

1682 //[[[end]]] 

1683 """, 

1684 

1685 'mysubmodule.py': """\ 

1686 import cog 

1687 cog.outl("Hello from mysubmodule") 

1688 """ 

1689 } 

1690 } 

1691 

1692 makeFiles(d) 

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

1694 # auto-include the current directory 

1695 self.cog.callableMain(['argv0', '-r', 'code/test.cog']) 

1696 self.assertFilesSame('code/test.cog', 'code/test.out') 

1697 

1698 

1699class CogTestsInFiles(TestCaseWithTempDir): 

1700 

1701 def testWarnIfNoCogCode(self): 

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

1703 d = { 

1704 'with.cog': """\ 

1705 //[[[cog 

1706 cog.outl("hello world") 

1707 //]]] 

1708 hello world 

1709 //[[[end]]] 

1710 """, 

1711 

1712 'without.cog': """\ 

1713 There's no cog 

1714 code in this file. 

1715 """, 

1716 } 

1717 

1718 makeFiles(d) 

1719 self.cog.callableMain(['argv0', '-e', 'with.cog']) 

1720 output = self.output.getvalue() 

1721 self.assertNotIn("Warning", output) 

1722 self.newCog() 

1723 self.cog.callableMain(['argv0', '-e', 'without.cog']) 

1724 output = self.output.getvalue() 

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

1726 self.newCog() 

1727 self.cog.callableMain(['argv0', 'without.cog']) 

1728 output = self.output.getvalue() 

1729 self.assertNotIn("Warning", output) 

1730 

1731 def testFileNameProps(self): 

1732 d = { 

1733 'cog1.txt': """\ 

1734 //[[[cog 

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

1736 //]]] 

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

1738 [[[end]]] 

1739 """, 

1740 

1741 'cog1.out': """\ 

1742 //[[[cog 

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

1744 //]]] 

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

1746 [[[end]]] 

1747 """, 

1748 

1749 'cog1out.out': """\ 

1750 //[[[cog 

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

1752 //]]] 

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

1754 [[[end]]] 

1755 """, 

1756 } 

1757 

1758 makeFiles(d) 

1759 self.cog.callableMain(['argv0', '-r', 'cog1.txt']) 

1760 self.assertFilesSame('cog1.txt', 'cog1.out') 

1761 self.newCog() 

1762 self.cog.callableMain(['argv0', '-o', 'cog1out.txt', 'cog1.txt']) 

1763 self.assertFilesSame('cog1out.txt', 'cog1out.out') 

1764 

1765 def testGlobalsDontCrossFiles(self): 

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

1767 d = { 

1768 'one.cog': """\ 

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

1770 //[[[end]]] 

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

1772 //[[[end]]] 

1773 """, 

1774 

1775 'one.out': """\ 

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

1777 //[[[end]]] 

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

1779 This was set in one.cog 

1780 //[[[end]]] 

1781 """, 

1782 

1783 'two.cog': """\ 

1784 //[[[cog 

1785 try: 

1786 cog.outl(s) 

1787 except NameError: 

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

1789 //]]] 

1790 //[[[end]]] 

1791 """, 

1792 

1793 'two.out': """\ 

1794 //[[[cog 

1795 try: 

1796 cog.outl(s) 

1797 except NameError: 

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

1799 //]]] 

1800 s isn't set! 

1801 //[[[end]]] 

1802 """, 

1803 

1804 'cogfiles.txt': """\ 

1805 # Please run cog 

1806 one.cog 

1807 

1808 two.cog 

1809 """ 

1810 } 

1811 

1812 makeFiles(d) 

1813 self.cog.callableMain(['argv0', '-r', '@cogfiles.txt']) 

1814 self.assertFilesSame('one.cog', 'one.out') 

1815 self.assertFilesSame('two.cog', 'two.out') 

1816 output = self.output.getvalue() 

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

1818 

1819 def testRemoveGeneratedOutput(self): 

1820 d = { 

1821 'cog1.txt': """\ 

1822 //[[[cog 

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

1824 //]]] 

1825 This line was generated. 

1826 //[[[end]]] 

1827 This line was not. 

1828 """, 

1829 

1830 'cog1.out': """\ 

1831 //[[[cog 

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

1833 //]]] 

1834 //[[[end]]] 

1835 This line was not. 

1836 """, 

1837 

1838 'cog1.out2': """\ 

1839 //[[[cog 

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

1841 //]]] 

1842 This line was generated. 

1843 //[[[end]]] 

1844 This line was not. 

1845 """, 

1846 } 

1847 

1848 makeFiles(d) 

1849 # Remove generated output. 

1850 self.cog.callableMain(['argv0', '-r', '-x', 'cog1.txt']) 

1851 self.assertFilesSame('cog1.txt', 'cog1.out') 

1852 self.newCog() 

1853 # Regenerate the generated output. 

1854 self.cog.callableMain(['argv0', '-r', 'cog1.txt']) 

1855 self.assertFilesSame('cog1.txt', 'cog1.out2') 

1856 self.newCog() 

1857 # Remove the generated output again. 

1858 self.cog.callableMain(['argv0', '-r', '-x', 'cog1.txt']) 

1859 self.assertFilesSame('cog1.txt', 'cog1.out') 

1860 

1861 def testMsgCall(self): 

1862 infile = """\ 

1863 #[[[cog 

1864 cog.msg("Hello there!") 

1865 #]]] 

1866 #[[[end]]] 

1867 """ 

1868 infile = reindentBlock(infile) 

1869 self.assertEqual(self.cog.processString(infile), infile) 

1870 output = self.output.getvalue() 

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

1872 

1873 def testErrorMessageHasNoTraceback(self): 

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

1875 

1876 d = { 

1877 'cog1.txt': """\ 

1878 //[[[cog 

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

1880 cog.outl("generated by cog") 

1881 cog.outl("blah blah.") 

1882 //]]] 

1883 Xhis line was newly 

1884 generated by cog 

1885 blah blah. 

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

1887 """, 

1888 } 

1889 

1890 makeFiles(d) 

1891 stderr = io.StringIO() 

1892 self.cog.setOutput(stderr=stderr) 

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

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

1895 self.assertEqual(stderr.getvalue(), "cog1.txt(9): Output has been edited! Delete old checksum to unprotect.\n") 

1896 

1897 def testDashD(self): 

1898 d = { 

1899 'test.cog': """\ 

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

1901 --[[[end]]] 

1902 """, 

1903 

1904 'test.kablooey': """\ 

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

1906 Defined fooey as kablooey 

1907 --[[[end]]] 

1908 """, 

1909 

1910 'test.einstein': """\ 

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

1912 Defined fooey as e=mc2 

1913 --[[[end]]] 

1914 """, 

1915 } 

1916 

1917 makeFiles(d) 

1918 self.cog.callableMain(['argv0', '-r', '-D', 'fooey=kablooey', 'test.cog']) 

1919 self.assertFilesSame('test.cog', 'test.kablooey') 

1920 makeFiles(d) 

1921 self.cog.callableMain(['argv0', '-r', '-Dfooey=kablooey', 'test.cog']) 

1922 self.assertFilesSame('test.cog', 'test.kablooey') 

1923 makeFiles(d) 

1924 self.cog.callableMain(['argv0', '-r', '-Dfooey=e=mc2', 'test.cog']) 

1925 self.assertFilesSame('test.cog', 'test.einstein') 

1926 makeFiles(d) 

1927 self.cog.callableMain(['argv0', '-r', '-Dbar=quux', '-Dfooey=kablooey', 'test.cog']) 

1928 self.assertFilesSame('test.cog', 'test.kablooey') 

1929 makeFiles(d) 

1930 self.cog.callableMain(['argv0', '-r', '-Dfooey=kablooey', '-Dbar=quux', 'test.cog']) 

1931 self.assertFilesSame('test.cog', 'test.kablooey') 

1932 makeFiles(d) 

1933 self.cog.callableMain(['argv0', '-r', '-Dfooey=gooey', '-Dfooey=kablooey', 'test.cog']) 

1934 self.assertFilesSame('test.cog', 'test.kablooey') 

1935 

1936 def testOutputToStdout(self): 

1937 d = { 

1938 'test.cog': """\ 

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

1940 --[[[end]]] 

1941 """ 

1942 } 

1943 

1944 makeFiles(d) 

1945 stderr = io.StringIO() 

1946 self.cog.setOutput(stderr=stderr) 

1947 self.cog.callableMain(['argv0', 'test.cog']) 

1948 output = self.output.getvalue() 

1949 outerr = stderr.getvalue() 

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

1951 self.assertEqual(outerr, "") 

1952 

1953 def testReadFromStdin(self): 

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

1955 def restore_stdin(old_stdin): 

1956 sys.stdin = old_stdin 

1957 self.addCleanup(restore_stdin, sys.stdin) 

1958 sys.stdin = stdin 

1959 

1960 stderr = io.StringIO() 

1961 self.cog.setOutput(stderr=stderr) 

1962 self.cog.callableMain(['argv0', '-']) 

1963 output = self.output.getvalue() 

1964 outerr = stderr.getvalue() 

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

1966 self.assertEqual(outerr, "") 

1967 

1968 def testSuffixOutputLines(self): 

1969 d = { 

1970 'test.cog': """\ 

1971 Hey there. 

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

1973 ;[[[end]]] 

1974 Good bye. 

1975 """, 

1976 

1977 'test.out': """\ 

1978 Hey there. 

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

1980 a (foo) 

1981 b (foo) 

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

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

1984 """ 

1985 c (foo) 

1986 ;[[[end]]] 

1987 Good bye. 

1988 """, 

1989 } 

1990 

1991 makeFiles(d) 

1992 self.cog.callableMain(['argv0', '-r', '-s', ' (foo)', 'test.cog']) 

1993 self.assertFilesSame('test.cog', 'test.out') 

1994 

1995 def testEmptySuffix(self): 

1996 d = { 

1997 'test.cog': """\ 

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

1999 ;[[[end]]] 

2000 """, 

2001 

2002 'test.out': """\ 

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

2004 a 

2005 b 

2006 c 

2007 ;[[[end]]] 

2008 """, 

2009 } 

2010 

2011 makeFiles(d) 

2012 self.cog.callableMain(['argv0', '-r', '-s', '', 'test.cog']) 

2013 self.assertFilesSame('test.cog', 'test.out') 

2014 

2015 def testHellishSuffix(self): 

2016 d = { 

2017 'test.cog': """\ 

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

2019 """, 

2020 

2021 'test.out': """\ 

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

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

2024 

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

2026 """, 

2027 } 

2028 

2029 makeFiles(d) 

2030 self.cog.callableMain(['argv0', '-z', '-r', '-s', r' /\n*+([)]><', 'test.cog']) 

2031 self.assertFilesSame('test.cog', 'test.out') 

2032 

2033 def testPrologue(self): 

2034 d = { 

2035 'test.cog': """\ 

2036 Some text. 

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

2038 //[[[end]]] 

2039 epilogue. 

2040 """, 

2041 

2042 'test.out': """\ 

2043 Some text. 

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

2045 1.4142135623 

2046 //[[[end]]] 

2047 epilogue. 

2048 """, 

2049 } 

2050 

2051 makeFiles(d) 

2052 self.cog.callableMain(['argv0', '-r', '-p', 'import math', 'test.cog']) 

2053 self.assertFilesSame('test.cog', 'test.out') 

2054 

2055 def testThreads(self): 

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

2057 # different threads. 

2058 numthreads = 20 

2059 

2060 d = {} 

2061 for i in range(numthreads): 

2062 d[f'f{i}.cog'] = ( 

2063 "x\n" * i + 

2064 "[[[cog\n" + 

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

2066 "]]]\n" + 

2067 "[[[end]]]\n" 

2068 ) 

2069 makeFiles(d) 

2070 

2071 results = [] 

2072 

2073 def thread_main(num): 

2074 try: 

2075 ret = Cog().main( 

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

2077 ) 

2078 assert ret == 0 

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

2080 results.append(exc) 

2081 else: 

2082 results.append(None) 

2083 

2084 ts = [threading.Thread(target=thread_main, args=(i,)) for i in range(numthreads)] 

2085 for t in ts: 

2086 t.start() 

2087 for t in ts: 

2088 t.join() 

2089 assert results == [None] * numthreads 

2090 

2091 

2092class CheckTests(TestCaseWithTempDir): 

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

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

2095 print(self.output.getvalue()) 

2096 self.assertEqual(status, actual_status) 

2097 

2098 def assert_made_files_unchanged(self, d): 

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

2100 content = reindentBlock(content) 

2101 if os.name == 'nt': 

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

2103 self.assertFileContent(name, content) 

2104 

2105 def test_check_no_cog(self): 

2106 d = { 

2107 'hello.txt': """\ 

2108 Hello. 

2109 """, 

2110 } 

2111 makeFiles(d) 

2112 self.run_check(['hello.txt'], status=0) 

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

2114 self.assert_made_files_unchanged(d) 

2115 

2116 def test_check_good(self): 

2117 d = { 

2118 'unchanged.cog': """\ 

2119 //[[[cog 

2120 cog.outl("hello world") 

2121 //]]] 

2122 hello world 

2123 //[[[end]]] 

2124 """, 

2125 } 

2126 makeFiles(d) 

2127 self.run_check(['unchanged.cog'], status=0) 

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

2129 self.assert_made_files_unchanged(d) 

2130 

2131 def test_check_bad(self): 

2132 d = { 

2133 'changed.cog': """\ 

2134 //[[[cog 

2135 cog.outl("goodbye world") 

2136 //]]] 

2137 hello world 

2138 //[[[end]]] 

2139 """, 

2140 } 

2141 makeFiles(d) 

2142 self.run_check(['changed.cog'], status=5) 

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

2144 self.assert_made_files_unchanged(d) 

2145 

2146 def test_check_mixed(self): 

2147 d = { 

2148 'unchanged.cog': """\ 

2149 //[[[cog 

2150 cog.outl("hello world") 

2151 //]]] 

2152 hello world 

2153 //[[[end]]] 

2154 """, 

2155 'changed.cog': """\ 

2156 //[[[cog 

2157 cog.outl("goodbye world") 

2158 //]]] 

2159 hello world 

2160 //[[[end]]] 

2161 """, 

2162 } 

2163 makeFiles(d) 

2164 for verbosity, output in [ 

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

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

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

2168 ]: 

2169 self.newCog() 

2170 self.run_check(['--verbosity=%s' % verbosity, 'unchanged.cog', 'changed.cog'], status=5) 

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

2172 self.assert_made_files_unchanged(d) 

2173 

2174 def test_check_with_good_checksum(self): 

2175 d = { 

2176 'good.txt': """\ 

2177 //[[[cog 

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

2179 cog.outl("generated by cog") 

2180 cog.outl("blah blah.") 

2181 //]]] 

2182 This line was newly 

2183 generated by cog 

2184 blah blah. 

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

2186 """, 

2187 } 

2188 makeFiles(d) 

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

2190 self.run_check(['-c', 'good.txt'], status=0) 

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

2192 self.assert_made_files_unchanged(d) 

2193 

2194 def test_check_with_bad_checksum(self): 

2195 d = { 

2196 'bad.txt': """\ 

2197 //[[[cog 

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

2199 cog.outl("generated by cog") 

2200 cog.outl("blah blah.") 

2201 //]]] 

2202 This line was newly 

2203 generated by cog 

2204 blah blah. 

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

2206 """, 

2207 } 

2208 makeFiles(d) 

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

2210 self.run_check(['-c', 'bad.txt'], status=1) 

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

2212 self.assert_made_files_unchanged(d) 

2213 

2214 

2215class WritabilityTests(TestCaseWithTempDir): 

2216 

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 

2226 'test.out': """\ 

2227 //[[[cog 

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

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

2230 //]]] 

2231 void DoSomething(); 

2232 void DoAnotherThing(); 

2233 void DoLastThing(); 

2234 //[[[end]]] 

2235 """, 

2236 } 

2237 

2238 if os.name == 'nt': 2238 ↛ 2240line 2238 didn't jump to line 2240, because the condition on line 2238 was never true

2239 # for Windows 

2240 cmd_w_args = 'attrib -R %s' 

2241 cmd_w_asterisk = 'attrib -R *' 

2242 else: 

2243 # for unix-like 

2244 cmd_w_args = 'chmod +w %s' 

2245 cmd_w_asterisk = 'chmod +w *' 

2246 

2247 def setUp(self): 

2248 super().setUp() 

2249 makeFiles(self.d) 

2250 self.testcog = os.path.join(self.tempdir, 'test.cog') 

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

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

2253 

2254 def tearDown(self): 

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

2256 super().tearDown() 

2257 

2258 def testReadonlyNoCommand(self): 

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

2260 self.cog.callableMain(['argv0', '-r', 'test.cog']) 

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

2262 

2263 def testReadonlyWithCommand(self): 

2264 self.cog.callableMain(['argv0', '-r', '-w', self.cmd_w_args, 'test.cog']) 

2265 self.assertFilesSame('test.cog', 'test.out') 

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

2267 

2268 def testReadonlyWithCommandWithNoSlot(self): 

2269 self.cog.callableMain(['argv0', '-r', '-w', self.cmd_w_asterisk, 'test.cog']) 

2270 self.assertFilesSame('test.cog', 'test.out') 

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

2272 

2273 def testReadonlyWithIneffectualCommand(self): 

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

2275 self.cog.callableMain(['argv0', '-r', '-w', 'echo %s', 'test.cog']) 

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

2277 

2278 

2279class ChecksumTests(TestCaseWithTempDir): 

2280 

2281 def testCreateChecksumOutput(self): 

2282 d = { 

2283 'cog1.txt': """\ 

2284 //[[[cog 

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

2286 //]]] 

2287 This line was generated. 

2288 //[[[end]]] 

2289 This line was not. 

2290 """, 

2291 

2292 'cog1.out': """\ 

2293 //[[[cog 

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

2295 //]]] 

2296 This line was generated. 

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

2298 This line was not. 

2299 """, 

2300 } 

2301 

2302 makeFiles(d) 

2303 self.cog.callableMain(['argv0', '-r', '-c', 'cog1.txt']) 

2304 self.assertFilesSame('cog1.txt', 'cog1.out') 

2305 

2306 def testCheckChecksumOutput(self): 

2307 d = { 

2308 'cog1.txt': """\ 

2309 //[[[cog 

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

2311 cog.outl("generated by cog") 

2312 cog.outl("blah blah.") 

2313 //]]] 

2314 This line was generated. 

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

2316 """, 

2317 

2318 'cog1.out': """\ 

2319 //[[[cog 

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

2321 cog.outl("generated by cog") 

2322 cog.outl("blah blah.") 

2323 //]]] 

2324 This line was newly 

2325 generated by cog 

2326 blah blah. 

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

2328 """, 

2329 } 

2330 

2331 makeFiles(d) 

2332 self.cog.callableMain(['argv0', '-r', '-c', 'cog1.txt']) 

2333 self.assertFilesSame('cog1.txt', 'cog1.out') 

2334 

2335 def testRemoveChecksumOutput(self): 

2336 d = { 

2337 'cog1.txt': """\ 

2338 //[[[cog 

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

2340 cog.outl("generated by cog") 

2341 cog.outl("blah blah.") 

2342 //]]] 

2343 This line was generated. 

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

2345 """, 

2346 

2347 'cog1.out': """\ 

2348 //[[[cog 

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

2350 cog.outl("generated by cog") 

2351 cog.outl("blah blah.") 

2352 //]]] 

2353 This line was newly 

2354 generated by cog 

2355 blah blah. 

2356 //[[[end]]] fooey 

2357 """, 

2358 } 

2359 

2360 makeFiles(d) 

2361 self.cog.callableMain(['argv0', '-r', 'cog1.txt']) 

2362 self.assertFilesSame('cog1.txt', 'cog1.out') 

2363 

2364 def testTamperedChecksumOutput(self): 

2365 d = { 

2366 'cog1.txt': """\ 

2367 //[[[cog 

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

2369 cog.outl("generated by cog") 

2370 cog.outl("blah blah.") 

2371 //]]] 

2372 Xhis line was newly 

2373 generated by cog 

2374 blah blah. 

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

2376 """, 

2377 

2378 'cog2.txt': """\ 

2379 //[[[cog 

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

2381 cog.outl("generated by cog") 

2382 cog.outl("blah blah.") 

2383 //]]] 

2384 This line was newly 

2385 generated by cog 

2386 blah blah! 

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

2388 """, 

2389 

2390 'cog3.txt': """\ 

2391 //[[[cog 

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

2393 cog.outl("generated by cog") 

2394 cog.outl("blah blah.") 

2395 //]]] 

2396 

2397 This line was newly 

2398 generated by cog 

2399 blah blah. 

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

2401 """, 

2402 

2403 'cog4.txt': """\ 

2404 //[[[cog 

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

2406 cog.outl("generated by cog") 

2407 cog.outl("blah blah.") 

2408 //]]] 

2409 This line was newly 

2410 generated by cog 

2411 blah blah.. 

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

2413 """, 

2414 

2415 'cog5.txt': """\ 

2416 //[[[cog 

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

2418 cog.outl("generated by cog") 

2419 cog.outl("blah blah.") 

2420 //]]] 

2421 This line was newly 

2422 generated by cog 

2423 blah blah. 

2424 extra 

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

2426 """, 

2427 

2428 'cog6.txt': """\ 

2429 //[[[cog 

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

2431 cog.outl("generated by cog") 

2432 cog.outl("blah blah.") 

2433 //]]] 

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

2435 """, 

2436 } 

2437 

2438 makeFiles(d) 

2439 with self.assertRaisesRegex(CogError, 

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

2441 self.cog.callableMain(['argv0', '-c', "cog1.txt"]) 

2442 with self.assertRaisesRegex(CogError, 

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

2444 self.cog.callableMain(['argv0', '-c', "cog2.txt"]) 

2445 with self.assertRaisesRegex(CogError, 

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

2447 self.cog.callableMain(['argv0', '-c', "cog3.txt"]) 

2448 with self.assertRaisesRegex(CogError, 

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

2450 self.cog.callableMain(['argv0', '-c', "cog4.txt"]) 

2451 with self.assertRaisesRegex(CogError, 

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

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

2454 with self.assertRaisesRegex(CogError, 

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

2456 self.cog.callableMain(['argv0', '-c', "cog6.txt"]) 

2457 

2458 def testArgvIsntModified(self): 

2459 argv = ['argv0', '-v'] 

2460 orig_argv = argv[:] 

2461 self.cog.callableMain(argv) 

2462 self.assertEqual(argv, orig_argv) 

2463 

2464 

2465class CustomMarkerTests(TestCaseWithTempDir): 

2466 

2467 def testCustomerMarkers(self): 

2468 d = { 

2469 'test.cog': """\ 

2470 //{{ 

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

2472 //}} 

2473 //{{end}} 

2474 """, 

2475 

2476 'test.out': """\ 

2477 //{{ 

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

2479 //}} 

2480 void MyFunction(); 

2481 //{{end}} 

2482 """, 

2483 } 

2484 

2485 makeFiles(d) 

2486 self.cog.callableMain([ 

2487 'argv0', '-r', 

2488 '--markers={{ }} {{end}}', 

2489 'test.cog' 

2490 ]) 

2491 self.assertFilesSame('test.cog', 'test.out') 

2492 

2493 def testTrulyWackyMarkers(self): 

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

2495 d = { 

2496 'test.cog': """\ 

2497 //**( 

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

2499 //**) 

2500 //**(end)** 

2501 """, 

2502 

2503 'test.out': """\ 

2504 //**( 

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

2506 //**) 

2507 void MyFunction(); 

2508 //**(end)** 

2509 """, 

2510 } 

2511 

2512 makeFiles(d) 

2513 self.cog.callableMain([ 

2514 'argv0', '-r', 

2515 '--markers=**( **) **(end)**', 

2516 'test.cog' 

2517 ]) 

2518 self.assertFilesSame('test.cog', 'test.out') 

2519 

2520 def testChangeJustOneMarker(self): 

2521 d = { 

2522 'test.cog': """\ 

2523 //**( 

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

2525 //]]] 

2526 //[[[end]]] 

2527 """, 

2528 

2529 'test.out': """\ 

2530 //**( 

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

2532 //]]] 

2533 void MyFunction(); 

2534 //[[[end]]] 

2535 """, 

2536 } 

2537 

2538 makeFiles(d) 

2539 self.cog.callableMain([ 

2540 'argv0', '-r', 

2541 '--markers=**( ]]] [[[end]]]', 

2542 'test.cog' 

2543 ]) 

2544 self.assertFilesSame('test.cog', 'test.out') 

2545 

2546 

2547class BlakeTests(TestCaseWithTempDir): 

2548 

2549 # Blake Winton's contributions. 

2550 def testDeleteCode(self): 

2551 # -o sets the output file. 

2552 d = { 

2553 'test.cog': """\ 

2554 // This is my C++ file. 

2555 //[[[cog 

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

2557 for fn in fnames: 

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

2559 //]]] 

2560 Some Sample Code Here 

2561 //[[[end]]]Data Data 

2562 And Some More 

2563 """, 

2564 

2565 'test.out': """\ 

2566 // This is my C++ file. 

2567 void DoSomething(); 

2568 void DoAnotherThing(); 

2569 void DoLastThing(); 

2570 And Some More 

2571 """, 

2572 } 

2573 

2574 makeFiles(d) 

2575 self.cog.callableMain(['argv0', '-d', '-o', 'test.cogged', 'test.cog']) 

2576 self.assertFilesSame('test.cogged', 'test.out') 

2577 

2578 def testDeleteCodeWithDashRFails(self): 

2579 d = { 

2580 'test.cog': """\ 

2581 // This is my C++ file. 

2582 """ 

2583 } 

2584 

2585 makeFiles(d) 

2586 with self.assertRaisesRegex(CogUsageError, r"^Can't use -d with -r \(or you would delete all your source!\)$"): 

2587 self.cog.callableMain(['argv0', '-r', '-d', 'test.cog']) 

2588 

2589 def testSettingGlobals(self): 

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

2591 # processFile(). 

2592 d = { 

2593 'test.cog': """\ 

2594 // This is my C++ file. 

2595 //[[[cog 

2596 for fn in fnames: 

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

2598 //]]] 

2599 Some Sample Code Here 

2600 //[[[end]]]""", 

2601 

2602 'test.out': """\ 

2603 // This is my C++ file. 

2604 void DoBlake(); 

2605 void DoWinton(); 

2606 void DoContribution(); 

2607 """, 

2608 } 

2609 

2610 makeFiles(d) 

2611 globals = {} 

2612 globals['fnames'] = ['DoBlake', 'DoWinton', 'DoContribution'] 

2613 self.cog.options.bDeleteCode = True 

2614 self.cog.processFile('test.cog', 'test.cogged', globals=globals) 

2615 self.assertFilesSame('test.cogged', 'test.out') 

2616 

2617 

2618class ErrorCallTests(TestCaseWithTempDir): 

2619 

2620 def testErrorCallHasNoTraceback(self): 

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

2622 d = { 

2623 'error.cog': """\ 

2624 //[[[cog 

2625 cog.error("Something Bad!") 

2626 //]]] 

2627 //[[[end]]] 

2628 """, 

2629 } 

2630 

2631 makeFiles(d) 

2632 self.cog.main(['argv0', '-r', 'error.cog']) 

2633 output = self.output.getvalue() 

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

2635 

2636 def testRealErrorHasTraceback(self): 

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

2638 d = { 

2639 'error.cog': """\ 

2640 //[[[cog 

2641 raise RuntimeError("Hey!") 

2642 //]]] 

2643 //[[[end]]] 

2644 """, 

2645 } 

2646 

2647 makeFiles(d) 

2648 self.cog.main(['argv0', '-r', 'error.cog']) 

2649 output = self.output.getvalue() 

2650 msg = 'Actual output:\n' + output 

2651 self.assertTrue(output.startswith("Cogging error.cog\nTraceback (most recent"), msg) 

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

2653 

2654 

2655# Things not yet tested: 

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