Coverage for cogapp / test_cogapp.py: 29.54%
911 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-09 06:46 -0500
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-09 06:46 -0500
1"""Test cogapp."""
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
15from .cogapp import Cog, CogOptions, CogGenerator
16from .cogapp import CogError, CogUsageError, CogGeneratedError, CogUserException
17from .cogapp import __version__, main
18from .hashhandler import HashHandler
19from .makefiles import make_files
20from .options import Markers, description
21from .whiteutils import reindent_block
24class CogTestsInMemory(TestCase):
25 """Test cases for cogapp.Cog()"""
27 def test_no_cog(self):
28 strings = [
29 "",
30 " ",
31 " \t \t \tx",
32 "hello",
33 "the cat\nin the\nhat.",
34 "Horton\n\tHears A\n\t\tWho",
35 ]
36 for s in strings:
37 self.assertEqual(Cog().process_string(s), s)
39 def test_simple(self):
40 infile = """\
41 Some text.
42 //[[[cog
43 import cog
44 cog.outl("This is line one\\n")
45 cog.outl("This is line two")
46 //]]]
47 gobbledegook.
48 //[[[end]]]
49 epilogue.
50 """
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
61 This is line two
62 //[[[end]]]
63 epilogue.
64 """
66 self.assertEqual(Cog().process_string(infile), outfile)
68 def test_empty_cog(self):
69 # The cog clause can be totally empty. Not sure why you'd want it,
70 # but it works.
71 infile = """\
72 hello
73 //[[[cog
74 //]]]
75 //[[[end]]]
76 goodbye
77 """
79 infile = reindent_block(infile)
80 self.assertEqual(Cog().process_string(infile), infile)
82 def test_multiple_cogs(self):
83 # One file can have many cog chunks, even abutting each other.
84 infile = """\
85 //[[[cog
86 cog.out("chunk1")
87 //]]]
88 chunk1
89 //[[[end]]]
90 //[[[cog
91 cog.out("chunk2")
92 //]]]
93 chunk2
94 //[[[end]]]
95 between chunks
96 //[[[cog
97 cog.out("chunk3")
98 //]]]
99 chunk3
100 //[[[end]]]
101 """
103 infile = reindent_block(infile)
104 self.assertEqual(Cog().process_string(infile), infile)
106 def test_trim_blank_lines(self):
107 infile = """\
108 //[[[cog
109 cog.out("This is line one\\n", trimblanklines=True)
110 cog.out('''
111 This is line two
112 ''', dedent=True, trimblanklines=True)
113 cog.outl("This is line three", trimblanklines=True)
114 //]]]
115 This is line one
116 This is line two
117 This is line three
118 //[[[end]]]
119 """
121 infile = reindent_block(infile)
122 self.assertEqual(Cog().process_string(infile), infile)
124 def test_trim_empty_blank_lines(self):
125 infile = """\
126 //[[[cog
127 cog.out("This is line one\\n", trimblanklines=True)
128 cog.out('''
129 This is line two
130 ''', dedent=True, trimblanklines=True)
131 cog.out('', dedent=True, trimblanklines=True)
132 cog.outl("This is line three", trimblanklines=True)
133 //]]]
134 This is line one
135 This is line two
136 This is line three
137 //[[[end]]]
138 """
140 infile = reindent_block(infile)
141 self.assertEqual(Cog().process_string(infile), infile)
143 def test_trim_blank_lines_with_last_partial(self):
144 infile = """\
145 //[[[cog
146 cog.out("This is line one\\n", trimblanklines=True)
147 cog.out("\\nLine two\\nLine three", trimblanklines=True)
148 //]]]
149 This is line one
150 Line two
151 Line three
152 //[[[end]]]
153 """
155 infile = reindent_block(infile)
156 self.assertEqual(Cog().process_string(infile), infile)
158 def test_cog_out_dedent(self):
159 infile = """\
160 //[[[cog
161 cog.out("This is the first line\\n")
162 cog.out('''
163 This is dedent=True 1
164 This is dedent=True 2
165 ''', dedent=True, trimblanklines=True)
166 cog.out('''
167 This is dedent=False 1
168 This is dedent=False 2
169 ''', dedent=False, trimblanklines=True)
170 cog.out('''
171 This is dedent=default 1
172 This is dedent=default 2
173 ''', trimblanklines=True)
174 cog.out("This is the last line\\n")
175 //]]]
176 This is the first line
177 This is dedent=True 1
178 This is dedent=True 2
179 This is dedent=False 1
180 This is dedent=False 2
181 This is dedent=default 1
182 This is dedent=default 2
183 This is the last line
184 //[[[end]]]
185 """
187 infile = reindent_block(infile)
188 self.assertEqual(Cog().process_string(infile), infile)
190 def test22_end_of_line(self):
191 # In Python 2.2, this cog file was not parsing because the
192 # last line is indented but didn't end with a newline.
193 infile = """\
194 //[[[cog
195 import cog
196 for i in range(3):
197 cog.out("%d\\n" % i)
198 //]]]
199 0
200 1
201 2
202 //[[[end]]]
203 """
205 infile = reindent_block(infile)
206 self.assertEqual(Cog().process_string(infile), infile)
208 def test_indented_code(self):
209 infile = """\
210 first line
211 [[[cog
212 import cog
213 for i in range(3):
214 cog.out("xx%d\\n" % i)
215 ]]]
216 xx0
217 xx1
218 xx2
219 [[[end]]]
220 last line
221 """
223 infile = reindent_block(infile)
224 self.assertEqual(Cog().process_string(infile), infile)
226 def test_prefixed_code(self):
227 infile = """\
228 --[[[cog
229 --import cog
230 --for i in range(3):
231 -- cog.out("xx%d\\n" % i)
232 --]]]
233 xx0
234 xx1
235 xx2
236 --[[[end]]]
237 """
239 infile = reindent_block(infile)
240 self.assertEqual(Cog().process_string(infile), infile)
242 def test_prefixed_indented_code(self):
243 infile = """\
244 prologue
245 --[[[cog
246 -- import cog
247 -- for i in range(3):
248 -- cog.out("xy%d\\n" % i)
249 --]]]
250 xy0
251 xy1
252 xy2
253 --[[[end]]]
254 """
256 infile = reindent_block(infile)
257 self.assertEqual(Cog().process_string(infile), infile)
259 def test_bogus_prefix_match(self):
260 infile = """\
261 prologue
262 #[[[cog
263 import cog
264 # This comment should not be clobbered by removing the pound sign.
265 for i in range(3):
266 cog.out("xy%d\\n" % i)
267 #]]]
268 xy0
269 xy1
270 xy2
271 #[[[end]]]
272 """
274 infile = reindent_block(infile)
275 self.assertEqual(Cog().process_string(infile), infile)
277 def test_no_final_newline(self):
278 # If the cog'ed output has no final newline,
279 # it shouldn't eat up the cog terminator.
280 infile = """\
281 prologue
282 [[[cog
283 import cog
284 for i in range(3):
285 cog.out("%d" % i)
286 ]]]
287 012
288 [[[end]]]
289 epilogue
290 """
292 infile = reindent_block(infile)
293 self.assertEqual(Cog().process_string(infile), infile)
295 def test_no_output_at_all(self):
296 # If there is absolutely no cog output, that's ok.
297 infile = """\
298 prologue
299 [[[cog
300 i = 1
301 ]]]
302 [[[end]]]
303 epilogue
304 """
306 infile = reindent_block(infile)
307 self.assertEqual(Cog().process_string(infile), infile)
309 def test_purely_blank_line(self):
310 # If there is a blank line in the cog code with no whitespace
311 # prefix, that should be OK.
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 """
326 infile = reindent_block(infile.replace("$", ""))
327 self.assertEqual(Cog().process_string(infile), infile)
329 def test_empty_outl(self):
330 # Alexander Belchenko suggested the string argument to outl should
331 # be optional. Does it work?
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
345 y
347 z
348 [[[end]]]
349 epilogue
350 """
352 infile = reindent_block(infile)
353 self.assertEqual(Cog().process_string(infile), infile)
355 def test_first_line_num(self):
356 infile = """\
357 fooey
358 [[[cog
359 cog.outl("started at line number %d" % cog.firstLineNum)
360 ]]]
361 started at line number 2
362 [[[end]]]
363 blah blah
364 [[[cog
365 cog.outl("and again at line %d" % cog.firstLineNum)
366 ]]]
367 and again at line 8
368 [[[end]]]
369 """
371 infile = reindent_block(infile)
372 self.assertEqual(Cog().process_string(infile), infile)
374 def test_compact_one_line_code(self):
375 infile = """\
376 first line
377 hey: [[[cog cog.outl("hello %d" % (3*3*3*3)) ]]] looky!
378 get rid of this!
379 [[[end]]]
380 last line
381 """
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 """
391 infile = reindent_block(infile)
392 self.assertEqual(Cog().process_string(infile), reindent_block(outfile))
394 def test_inside_out_compact(self):
395 infile = """\
396 first line
397 hey?: ]]] what is this? [[[cog strange!
398 get rid of this!
399 [[[end]]]
400 last line
401 """
402 with self.assertRaisesRegex(
403 CogError, r"^infile.txt\(2\): Cog code markers inverted$"
404 ):
405 Cog().process_string(reindent_block(infile), "infile.txt")
407 def test_sharing_globals(self):
408 infile = """\
409 first line
410 hey: [[[cog s="hey there" ]]] looky!
411 [[[end]]]
412 more literal junk.
413 [[[cog cog.outl(s) ]]]
414 [[[end]]]
415 last line
416 """
418 outfile = """\
419 first line
420 hey: [[[cog s="hey there" ]]] looky!
421 [[[end]]]
422 more literal junk.
423 [[[cog cog.outl(s) ]]]
424 hey there
425 [[[end]]]
426 last line
427 """
429 infile = reindent_block(infile)
430 self.assertEqual(Cog().process_string(infile), reindent_block(outfile))
432 def test_assert_in_cog_code(self):
433 # Check that we can test assertions in cog code in the test framework.
434 infile = """\
435 [[[cog
436 assert 1 == 2, "Oops"
437 ]]]
438 [[[end]]]
439 """
440 infile = reindent_block(infile)
441 with self.assertRaisesRegex(CogUserException, "AssertionError: Oops"):
442 Cog().process_string(infile)
444 def test_cog_previous(self):
445 # Check that we can access the previous run's output.
446 infile = """\
447 [[[cog
448 assert cog.previous == "Hello there!\\n", "WTF??"
449 cog.out(cog.previous)
450 cog.outl("Ran again!")
451 ]]]
452 Hello there!
453 [[[end]]]
454 """
456 outfile = """\
457 [[[cog
458 assert cog.previous == "Hello there!\\n", "WTF??"
459 cog.out(cog.previous)
460 cog.outl("Ran again!")
461 ]]]
462 Hello there!
463 Ran again!
464 [[[end]]]
465 """
467 infile = reindent_block(infile)
468 self.assertEqual(Cog().process_string(infile), reindent_block(outfile))
470 def test_unrelated_triple_closing_brackets(self):
471 # Python code with accurate type annotations may have triple closing brackets.
472 # The Python code may be embedded in other files with cog usage,
473 # such as RST files or GitHub workflows. Cog must not error on this input.
474 # The example below is a fully-valid, executable GitHub workflow.
475 infile = """\
476 on:
477 push:
478 jobs:
479 demo:
480 runs-on: ubuntu-latest
481 steps:
482 - shell: bash
483 run: |
484 # [[[cog cog.outl("echo 'hello'") ]]]
485 echo 'hello'
486 # [[[end]]]
487 - shell: python
488 run: |
489 def demo() -> tuple[tuple[tuple[str]]]:
490 # This is what's being tested ^^^
491 #
492 return ((("hello world",),),)
493 print(demo()[0][0][0])
494 """
496 infile = reindent_block(infile)
497 self.assertEqual(Cog().process_string(infile), infile)
500class CogOptionsTests(TestCase):
501 """Test the CogOptions class."""
503 def test_equality(self):
504 o = CogOptions()
505 p = CogOptions()
506 self.assertEqual(o, p)
507 o.parse_args(["-r"])
508 self.assertNotEqual(o, p)
509 p.parse_args(["-r"])
510 self.assertEqual(o, p)
512 def test_cloning(self):
513 o = CogOptions()
514 o.parse_args(["-I", "fooey", "-I", "booey", "-s", " /*x*/"])
515 p = o.clone()
516 self.assertEqual(o, p)
517 p.parse_args(["-I", "huey", "-D", "foo=quux"])
518 self.assertNotEqual(o, p)
519 q = CogOptions()
520 q.parse_args(
521 [
522 "-I",
523 "fooey",
524 "-I",
525 "booey",
526 "-s",
527 " /*x*/",
528 "-I",
529 "huey",
530 "-D",
531 "foo=quux",
532 ]
533 )
534 self.assertEqual(p, q)
536 def test_combining_flags(self):
537 # Single-character flags can be combined.
538 o = CogOptions()
539 o.parse_args(["-e", "-r", "-z"])
540 p = CogOptions()
541 p.parse_args(["-erz"])
542 self.assertEqual(o, p)
544 def test_markers(self):
545 o = Markers.from_arg("a b c")
546 self.assertEqual("a", o.begin_spec)
547 self.assertEqual("b", o.end_spec)
548 self.assertEqual("c", o.end_output)
550 def test_markers_switch(self):
551 o = CogOptions()
552 o.parse_args(["--markers", "a b c"])
553 self.assertEqual("a", o.begin_spec)
554 self.assertEqual("b", o.end_spec)
555 self.assertEqual("c", o.end_output)
558class FileStructureTests(TestCase):
559 """Test that we're properly strict about the structure of files."""
561 def is_bad(self, infile, msg=None):
562 infile = reindent_block(infile)
563 with self.assertRaisesRegex(CogError, "^" + re.escape(msg) + "$"):
564 Cog().process_string(infile, "infile.txt")
566 def test_begin_no_end(self):
567 infile = """\
568 Fooey
569 #[[[cog
570 cog.outl('hello')
571 """
572 self.is_bad(infile, "infile.txt(2): Cog block begun but never ended.")
574 def test_no_eoo(self):
575 infile = """\
576 Fooey
577 #[[[cog
578 cog.outl('hello')
579 #]]]
580 """
581 self.is_bad(infile, "infile.txt(4): Missing '[[[end]]]' before end of file.")
583 infile2 = """\
584 Fooey
585 #[[[cog
586 cog.outl('hello')
587 #]]]
588 #[[[cog
589 cog.outl('goodbye')
590 #]]]
591 """
592 self.is_bad(infile2, "infile.txt(5): Unexpected '[[[cog'")
594 def test_start_with_eoo(self):
595 infile = """\
596 #[[[end]]]
597 """
598 self.is_bad(infile, "infile.txt(1): Unexpected '[[[end]]]'")
600 infile2 = """\
601 #[[[cog
602 cog.outl('hello')
603 #]]]
604 #[[[end]]]
605 #[[[end]]]
606 """
607 self.is_bad(infile2, "infile.txt(5): Unexpected '[[[end]]]'")
609 def test_no_end(self):
610 infile = """\
611 #[[[cog
612 cog.outl("hello")
613 #[[[end]]]
614 """
615 self.is_bad(infile, "infile.txt(3): Unexpected '[[[end]]]'")
617 infile2 = """\
618 #[[[cog
619 cog.outl('hello')
620 #]]]
621 #[[[end]]]
622 #[[[cog
623 cog.outl("hello")
624 #[[[end]]]
625 """
626 self.is_bad(infile2, "infile.txt(7): Unexpected '[[[end]]]'")
628 def test_two_begins(self):
629 infile = """\
630 #[[[cog
631 #[[[cog
632 cog.outl("hello")
633 #]]]
634 #[[[end]]]
635 """
636 self.is_bad(infile, "infile.txt(2): Unexpected '[[[cog'")
638 infile2 = """\
639 #[[[cog
640 cog.outl("hello")
641 #]]]
642 #[[[end]]]
643 #[[[cog
644 #[[[cog
645 cog.outl("hello")
646 #]]]
647 #[[[end]]]
648 """
649 self.is_bad(infile2, "infile.txt(6): Unexpected '[[[cog'")
651 def test_two_ends(self):
652 infile = """\
653 #[[[cog
654 cog.outl("hello")
655 #]]]
656 #]]]
657 #[[[end]]]
658 """
659 self.is_bad(infile, "infile.txt(4): Unexpected ']]]'")
661 infile2 = """\
662 #[[[cog
663 cog.outl("hello")
664 #]]]
665 #[[[end]]]
666 #[[[cog
667 cog.outl("hello")
668 #]]]
669 #]]]
670 #[[[end]]]
671 """
672 self.is_bad(infile2, "infile.txt(8): Unexpected ']]]'")
675class CogErrorTests(TestCase):
676 """Test cases for cog.error()."""
678 def test_error_msg(self):
679 infile = """\
680 [[[cog cog.error("This ain't right!")]]]
681 [[[end]]]
682 """
684 infile = reindent_block(infile)
685 with self.assertRaisesRegex(CogGeneratedError, "^This ain't right!$"):
686 Cog().process_string(infile)
688 def test_error_no_msg(self):
689 infile = """\
690 [[[cog cog.error()]]]
691 [[[end]]]
692 """
694 infile = reindent_block(infile)
695 with self.assertRaisesRegex(
696 CogGeneratedError, "^Error raised by cog generator.$"
697 ):
698 Cog().process_string(infile)
700 def test_no_error_if_error_not_called(self):
701 infile = """\
702 --[[[cog
703 --import cog
704 --for i in range(3):
705 -- if i > 10:
706 -- cog.error("Something is amiss!")
707 -- cog.out("xx%d\\n" % i)
708 --]]]
709 xx0
710 xx1
711 xx2
712 --[[[end]]]
713 """
715 infile = reindent_block(infile)
716 self.assertEqual(Cog().process_string(infile), infile)
719class CogGeneratorGetCodeTests(TestCase):
720 """Tests for CogGenerator.getCode()."""
722 def setUp(self):
723 # All tests get a generator to use, and short same-length names for
724 # the functions we're going to use.
725 self.gen = CogGenerator()
726 self.m = self.gen.parse_marker
727 self.parse_line = self.gen.parse_line
729 def test_empty(self):
730 self.m("// [[[cog")
731 self.m("// ]]]")
732 self.assertEqual(self.gen.get_code(), "")
734 def test_simple(self):
735 self.m("// [[[cog")
736 self.parse_line(' print "hello"')
737 self.parse_line(' print "bye"')
738 self.m("// ]]]")
739 self.assertEqual(self.gen.get_code(), 'print "hello"\nprint "bye"')
741 def test_compressed1(self):
742 # For a while, I supported compressed code blocks, but no longer.
743 self.m('// [[[cog: print """')
744 self.parse_line("// hello")
745 self.parse_line("// bye")
746 self.m('// """)]]]')
747 self.assertEqual(self.gen.get_code(), "hello\nbye")
749 def test_compressed2(self):
750 # For a while, I supported compressed code blocks, but no longer.
751 self.m('// [[[cog: print """')
752 self.parse_line("hello")
753 self.parse_line("bye")
754 self.m('// """)]]]')
755 self.assertEqual(self.gen.get_code(), "hello\nbye")
757 def test_compressed3(self):
758 # For a while, I supported compressed code blocks, but no longer.
759 self.m("// [[[cog")
760 self.parse_line('print """hello')
761 self.parse_line("bye")
762 self.m('// """)]]]')
763 self.assertEqual(self.gen.get_code(), 'print """hello\nbye')
765 def test_compressed4(self):
766 # For a while, I supported compressed code blocks, but no longer.
767 self.m('// [[[cog: print """')
768 self.parse_line("hello")
769 self.parse_line('bye""")')
770 self.m("// ]]]")
771 self.assertEqual(self.gen.get_code(), 'hello\nbye""")')
773 def test_no_common_prefix_for_markers(self):
774 # It's important to be able to use #if 0 to hide lines from a
775 # C++ compiler.
776 self.m("#if 0 //[[[cog")
777 self.parse_line("\timport cog, sys")
778 self.parse_line("")
779 self.parse_line("\tprint sys.argv")
780 self.m("#endif //]]]")
781 self.assertEqual(self.gen.get_code(), "import cog, sys\n\nprint sys.argv")
784class TestCaseWithTempDir(TestCase):
785 def new_cog(self):
786 """Initialize the cog members for another run."""
787 # Create a cog engine, and catch its output.
788 self.cog = Cog()
789 self.output = io.StringIO()
790 self.cog.set_output(stdout=self.output, stderr=self.output)
792 def setUp(self):
793 # Create a temporary directory.
794 self.tempdir = os.path.join(
795 tempfile.gettempdir(), "testcog_tempdir_" + str(random.random())[2:]
796 )
797 os.mkdir(self.tempdir)
798 self.olddir = os.getcwd()
799 os.chdir(self.tempdir)
800 self.new_cog()
802 def tearDown(self):
803 os.chdir(self.olddir)
804 # Get rid of the temporary directory.
805 shutil.rmtree(self.tempdir)
807 def assertFilesSame(self, file_name1, file_name2):
808 with open(os.path.join(self.tempdir, file_name1), "rb") as f1:
809 text1 = f1.read()
810 with open(os.path.join(self.tempdir, file_name2), "rb") as f2:
811 text2 = f2.read()
812 self.assertEqual(text1, text2)
814 def assertFileContent(self, fname, content):
815 absname = os.path.join(self.tempdir, fname)
816 with open(absname, "rb") as f:
817 file_content = f.read()
818 self.assertEqual(file_content, content.encode("utf-8"))
821class ArgumentHandlingTests(TestCaseWithTempDir):
822 def test_argument_failure(self):
823 # Return value 2 means usage problem.
824 self.assertEqual(self.cog.main(["argv0", "-j"]), 2)
825 output = self.output.getvalue()
826 self.assertIn("unrecognized arguments: -j", output)
827 with self.assertRaisesRegex(CogUsageError, r"^No files to process$"):
828 self.cog.callable_main(["argv0"])
829 with self.assertRaisesRegex(CogUsageError, r"^unrecognized arguments: -j$"):
830 self.cog.callable_main(["argv0", "-j"])
832 def test_no_dash_o_and_at_file(self):
833 make_files({"cogfiles.txt": "# Please run cog"})
834 with self.assertRaisesRegex(CogUsageError, r"^Can't use -o with @file$"):
835 self.cog.callable_main(["argv0", "-o", "foo", "@cogfiles.txt"])
837 def test_no_dash_o_and_amp_file(self):
838 make_files({"cogfiles.txt": "# Please run cog"})
839 with self.assertRaisesRegex(CogUsageError, r"^Can't use -o with &file$"):
840 self.cog.callable_main(["argv0", "-o", "foo", "&cogfiles.txt"])
842 def test_no_diff_without_check(self):
843 with self.assertRaisesRegex(
844 CogUsageError, r"^Can't use --diff without --check$"
845 ):
846 self.cog.callable_main(["argv0", "--diff"])
848 def test_dash_v(self):
849 self.assertEqual(self.cog.main(["argv0", "-v"]), 0)
850 output = self.output.getvalue()
851 self.assertEqual("Cog version %s\n" % __version__, output)
853 def produces_help(self, args):
854 self.new_cog()
855 argv = ["argv0"] + args.split()
856 self.assertEqual(self.cog.main(argv), 0)
857 output = self.output.getvalue()
858 self.assertRegex(output, f"^{re.escape(description)}.*")
860 def test_dash_h(self):
861 # -h, --help, or -? anywhere on the command line should just print help.
862 self.produces_help("-h")
863 self.produces_help("--help")
864 self.produces_help("-?")
865 self.produces_help("fooey.txt -h")
866 self.produces_help("fooey.txt --help")
867 self.produces_help("-o -r @fooey.txt -? @booey.txt")
869 def test_dash_o_and_dash_r(self):
870 d = {
871 "cogfile.txt": """\
872 # Please run cog
873 """
874 }
876 make_files(d)
877 with self.assertRaisesRegex(
878 CogUsageError, r"^Can't use -o with -r \(they are opposites\)$"
879 ):
880 self.cog.callable_main(["argv0", "-o", "foo", "-r", "cogfile.txt"])
882 def test_dash_z(self):
883 d = {
884 "test.cog": """\
885 // This is my C++ file.
886 //[[[cog
887 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
888 for fn in fnames:
889 cog.outl("void %s();" % fn)
890 //]]]
891 """,
892 "test.out": """\
893 // This is my C++ file.
894 //[[[cog
895 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
896 for fn in fnames:
897 cog.outl("void %s();" % fn)
898 //]]]
899 void DoSomething();
900 void DoAnotherThing();
901 void DoLastThing();
902 """,
903 }
905 make_files(d)
906 with self.assertRaisesRegex(
907 CogError, r"^test.cog\(6\): Missing '\[\[\[end\]\]\]' before end of file.$"
908 ):
909 self.cog.callable_main(["argv0", "-r", "test.cog"])
910 self.new_cog()
911 self.cog.callable_main(["argv0", "-r", "-z", "test.cog"])
912 self.assertFilesSame("test.cog", "test.out")
914 def test_bad_dash_d(self):
915 with self.assertRaisesRegex(
916 CogUsageError, r"^argument -D: takes a name=value argument$"
917 ):
918 self.cog.callable_main(["argv0", "-Dfooey", "cog.txt"])
919 with self.assertRaisesRegex(
920 CogUsageError, r"^argument -D: takes a name=value argument$"
921 ):
922 self.cog.callable_main(["argv0", "-D", "fooey", "cog.txt"])
924 def test_bad_markers(self):
925 with self.assertRaisesRegex(
926 CogUsageError,
927 r"^argument --markers: requires 3 values separated by spaces, could not parse 'X'$",
928 ):
929 self.cog.callable_main(["argv0", "--markers=X"])
930 with self.assertRaisesRegex(
931 CogUsageError,
932 r"^argument --markers: requires 3 values separated by spaces, could not parse 'A B C D'$",
933 ):
934 self.cog.callable_main(["argv0", "--markers=A B C D"])
937class TestMain(TestCaseWithTempDir):
938 def setUp(self):
939 super().setUp()
940 self.old_argv = sys.argv[:]
941 self.old_stderr = sys.stderr
942 sys.stderr = io.StringIO()
944 def tearDown(self):
945 sys.stderr = self.old_stderr
946 sys.argv = self.old_argv
947 sys.modules.pop("mycode", None)
948 super().tearDown()
950 def test_main_function(self):
951 sys.argv = ["argv0", "-Z"]
952 ret = main()
953 self.assertEqual(ret, 2)
954 stderr = sys.stderr.getvalue()
955 self.assertEqual(stderr, "unrecognized arguments: -Z\n(for help use --help)\n")
957 files = {
958 "test.cog": """\
959 //[[[cog
960 def func():
961 import mycode
962 mycode.boom()
963 //]]]
964 //[[[end]]]
965 -----
966 //[[[cog
967 func()
968 //]]]
969 //[[[end]]]
970 """,
971 "mycode.py": """\
972 def boom():
973 [][0]
974 """,
975 }
977 def test_error_report(self):
978 self.check_error_report()
980 def test_error_report_with_prologue(self):
981 self.check_error_report("-p", "#1\n#2")
983 def check_error_report(self, *args):
984 """Check that the error report is right."""
985 make_files(self.files)
986 sys.argv = ["argv0"] + list(args) + ["-r", "test.cog"]
987 main()
988 expected = reindent_block("""\
989 Traceback (most recent call last):
990 File "test.cog", line 9, in <module>
991 func()
992 File "test.cog", line 4, in func
993 mycode.boom()
994 File "MYCODE", line 2, in boom
995 [][0]
996 IndexError: list index out of range
997 """)
998 expected = expected.replace("MYCODE", os.path.abspath("mycode.py"))
999 assert expected == sys.stderr.getvalue()
1001 def test_error_in_prologue(self):
1002 make_files(self.files)
1003 sys.argv = ["argv0", "-p", "import mycode; mycode.boom()", "-r", "test.cog"]
1004 main()
1005 expected = reindent_block("""\
1006 Traceback (most recent call last):
1007 File "<prologue>", line 1, in <module>
1008 import mycode; mycode.boom()
1009 File "MYCODE", line 2, in boom
1010 [][0]
1011 IndexError: list index out of range
1012 """)
1013 expected = expected.replace("MYCODE", os.path.abspath("mycode.py"))
1014 assert expected == sys.stderr.getvalue()
1017class TestFileHandling(TestCaseWithTempDir):
1018 def test_simple(self):
1019 d = {
1020 "test.cog": """\
1021 // This is my C++ file.
1022 //[[[cog
1023 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1024 for fn in fnames:
1025 cog.outl("void %s();" % fn)
1026 //]]]
1027 //[[[end]]]
1028 """,
1029 "test.out": """\
1030 // This is my C++ file.
1031 //[[[cog
1032 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1033 for fn in fnames:
1034 cog.outl("void %s();" % fn)
1035 //]]]
1036 void DoSomething();
1037 void DoAnotherThing();
1038 void DoLastThing();
1039 //[[[end]]]
1040 """,
1041 }
1043 make_files(d)
1044 self.cog.callable_main(["argv0", "-r", "test.cog"])
1045 self.assertFilesSame("test.cog", "test.out")
1046 output = self.output.getvalue()
1047 self.assertIn("(changed)", output)
1049 def test_print_output(self):
1050 d = {
1051 "test.cog": """\
1052 // This is my C++ file.
1053 //[[[cog
1054 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1055 for fn in fnames:
1056 print("void %s();" % fn)
1057 //]]]
1058 //[[[end]]]
1059 """,
1060 "test.out": """\
1061 // This is my C++ file.
1062 //[[[cog
1063 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1064 for fn in fnames:
1065 print("void %s();" % fn)
1066 //]]]
1067 void DoSomething();
1068 void DoAnotherThing();
1069 void DoLastThing();
1070 //[[[end]]]
1071 """,
1072 }
1074 make_files(d)
1075 self.cog.callable_main(["argv0", "-rP", "test.cog"])
1076 self.assertFilesSame("test.cog", "test.out")
1077 output = self.output.getvalue()
1078 self.assertIn("(changed)", output)
1080 def test_wildcards(self):
1081 d = {
1082 "test.cog": """\
1083 // This is my C++ file.
1084 //[[[cog
1085 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1086 for fn in fnames:
1087 cog.outl("void %s();" % fn)
1088 //]]]
1089 //[[[end]]]
1090 """,
1091 "test2.cog": """\
1092 // This is my C++ file.
1093 //[[[cog
1094 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1095 for fn in fnames:
1096 cog.outl("void %s();" % fn)
1097 //]]]
1098 //[[[end]]]
1099 """,
1100 "test.out": """\
1101 // This is my C++ file.
1102 //[[[cog
1103 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1104 for fn in fnames:
1105 cog.outl("void %s();" % fn)
1106 //]]]
1107 void DoSomething();
1108 void DoAnotherThing();
1109 void DoLastThing();
1110 //[[[end]]]
1111 """,
1112 "not_this_one.cog": """\
1113 // This is my C++ file.
1114 //[[[cog
1115 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1116 for fn in fnames:
1117 cog.outl("void %s();" % fn)
1118 //]]]
1119 //[[[end]]]
1120 """,
1121 "not_this_one.out": """\
1122 // This is my C++ file.
1123 //[[[cog
1124 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1125 for fn in fnames:
1126 cog.outl("void %s();" % fn)
1127 //]]]
1128 //[[[end]]]
1129 """,
1130 }
1132 make_files(d)
1133 self.cog.callable_main(["argv0", "-r", "t*.cog"])
1134 self.assertFilesSame("test.cog", "test.out")
1135 self.assertFilesSame("test2.cog", "test.out")
1136 self.assertFilesSame("not_this_one.cog", "not_this_one.out")
1137 output = self.output.getvalue()
1138 self.assertIn("(changed)", output)
1140 def test_output_file(self):
1141 # -o sets the output file.
1142 d = {
1143 "test.cog": """\
1144 // This is my C++ file.
1145 //[[[cog
1146 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1147 for fn in fnames:
1148 cog.outl("void %s();" % fn)
1149 //]]]
1150 //[[[end]]]
1151 """,
1152 "test.out": """\
1153 // This is my C++ file.
1154 //[[[cog
1155 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1156 for fn in fnames:
1157 cog.outl("void %s();" % fn)
1158 //]]]
1159 void DoSomething();
1160 void DoAnotherThing();
1161 void DoLastThing();
1162 //[[[end]]]
1163 """,
1164 }
1166 make_files(d)
1167 self.cog.callable_main(["argv0", "-o", "in/a/dir/test.cogged", "test.cog"])
1168 self.assertFilesSame("in/a/dir/test.cogged", "test.out")
1170 def test_at_file(self):
1171 d = {
1172 "one.cog": """\
1173 //[[[cog
1174 cog.outl("hello world")
1175 //]]]
1176 //[[[end]]]
1177 """,
1178 "one.out": """\
1179 //[[[cog
1180 cog.outl("hello world")
1181 //]]]
1182 hello world
1183 //[[[end]]]
1184 """,
1185 "two.cog": """\
1186 //[[[cog
1187 cog.outl("goodbye cruel world")
1188 //]]]
1189 //[[[end]]]
1190 """,
1191 "two.out": """\
1192 //[[[cog
1193 cog.outl("goodbye cruel world")
1194 //]]]
1195 goodbye cruel world
1196 //[[[end]]]
1197 """,
1198 "cogfiles.txt": """\
1199 # Please run cog
1200 one.cog
1202 two.cog
1203 """,
1204 }
1206 make_files(d)
1207 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1208 self.assertFilesSame("one.cog", "one.out")
1209 self.assertFilesSame("two.cog", "two.out")
1210 output = self.output.getvalue()
1211 self.assertIn("(changed)", output)
1213 def test_nested_at_file(self):
1214 d = {
1215 "one.cog": """\
1216 //[[[cog
1217 cog.outl("hello world")
1218 //]]]
1219 //[[[end]]]
1220 """,
1221 "one.out": """\
1222 //[[[cog
1223 cog.outl("hello world")
1224 //]]]
1225 hello world
1226 //[[[end]]]
1227 """,
1228 "two.cog": """\
1229 //[[[cog
1230 cog.outl("goodbye cruel world")
1231 //]]]
1232 //[[[end]]]
1233 """,
1234 "two.out": """\
1235 //[[[cog
1236 cog.outl("goodbye cruel world")
1237 //]]]
1238 goodbye cruel world
1239 //[[[end]]]
1240 """,
1241 "cogfiles.txt": """\
1242 # Please run cog
1243 one.cog
1244 @cogfiles2.txt
1245 """,
1246 "cogfiles2.txt": """\
1247 # This one too, please.
1248 two.cog
1249 """,
1250 }
1252 make_files(d)
1253 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1254 self.assertFilesSame("one.cog", "one.out")
1255 self.assertFilesSame("two.cog", "two.out")
1256 output = self.output.getvalue()
1257 self.assertIn("(changed)", output)
1259 def test_at_file_with_args(self):
1260 d = {
1261 "both.cog": """\
1262 //[[[cog
1263 cog.outl("one: %s" % ('one' in globals()))
1264 cog.outl("two: %s" % ('two' in globals()))
1265 //]]]
1266 //[[[end]]]
1267 """,
1268 "one.out": """\
1269 //[[[cog
1270 cog.outl("one: %s" % ('one' in globals()))
1271 cog.outl("two: %s" % ('two' in globals()))
1272 //]]]
1273 one: True // ONE
1274 two: False // ONE
1275 //[[[end]]]
1276 """,
1277 "two.out": """\
1278 //[[[cog
1279 cog.outl("one: %s" % ('one' in globals()))
1280 cog.outl("two: %s" % ('two' in globals()))
1281 //]]]
1282 one: False // TWO
1283 two: True // TWO
1284 //[[[end]]]
1285 """,
1286 "cogfiles.txt": """\
1287 # Please run cog
1288 both.cog -o in/a/dir/both.one -s ' // ONE' -D one=x
1289 both.cog -o in/a/dir/both.two -s ' // TWO' -D two=x
1290 """,
1291 }
1293 make_files(d)
1294 self.cog.callable_main(["argv0", "@cogfiles.txt"])
1295 self.assertFilesSame("in/a/dir/both.one", "one.out")
1296 self.assertFilesSame("in/a/dir/both.two", "two.out")
1298 def test_at_file_with_bad_arg_combo(self):
1299 d = {
1300 "both.cog": """\
1301 //[[[cog
1302 cog.outl("one: %s" % ('one' in globals()))
1303 cog.outl("two: %s" % ('two' in globals()))
1304 //]]]
1305 //[[[end]]]
1306 """,
1307 "cogfiles.txt": """\
1308 # Please run cog
1309 both.cog
1310 both.cog -d # This is bad: -r and -d
1311 """,
1312 }
1314 make_files(d)
1315 with self.assertRaisesRegex(
1316 CogUsageError,
1317 r"^Can't use -d with -r \(or you would delete all your source!\)$",
1318 ):
1319 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1321 def test_at_file_with_tricky_filenames(self):
1322 def fix_backslashes(files_txt):
1323 """Make the contents of a files.txt sensitive to the platform."""
1324 if sys.platform != "win32":
1325 files_txt = files_txt.replace("\\", "/")
1326 return files_txt
1328 d = {
1329 "one 1.cog": """\
1330 //[[[cog cog.outl("hello world") ]]]
1331 """,
1332 "one.out": """\
1333 //[[[cog cog.outl("hello world") ]]]
1334 hello world //xxx
1335 """,
1336 "subdir": {
1337 "subback.cog": """\
1338 //[[[cog cog.outl("down deep with backslashes") ]]]
1339 """,
1340 "subfwd.cog": """\
1341 //[[[cog cog.outl("down deep with slashes") ]]]
1342 """,
1343 },
1344 "subback.out": """\
1345 //[[[cog cog.outl("down deep with backslashes") ]]]
1346 down deep with backslashes //yyy
1347 """,
1348 "subfwd.out": """\
1349 //[[[cog cog.outl("down deep with slashes") ]]]
1350 down deep with slashes //zzz
1351 """,
1352 "cogfiles.txt": fix_backslashes("""\
1353 # Please run cog
1354 'one 1.cog' -s ' //xxx'
1355 subdir\\subback.cog -s ' //yyy'
1356 subdir/subfwd.cog -s ' //zzz'
1357 """),
1358 }
1360 make_files(d)
1361 self.cog.callable_main(["argv0", "-z", "-r", "@cogfiles.txt"])
1362 self.assertFilesSame("one 1.cog", "one.out")
1363 self.assertFilesSame("subdir/subback.cog", "subback.out")
1364 self.assertFilesSame("subdir/subfwd.cog", "subfwd.out")
1366 def test_amp_file(self):
1367 d = {
1368 "code": {
1369 "files_to_cog": """\
1370 # A locally resolved file name.
1371 test.cog
1372 """,
1373 "test.cog": """\
1374 //[[[cog
1375 import myampsubmodule
1376 //]]]
1377 //[[[end]]]
1378 """,
1379 "test.out": """\
1380 //[[[cog
1381 import myampsubmodule
1382 //]]]
1383 Hello from myampsubmodule
1384 //[[[end]]]
1385 """,
1386 "myampsubmodule.py": """\
1387 import cog
1388 cog.outl("Hello from myampsubmodule")
1389 """,
1390 }
1391 }
1393 make_files(d)
1394 print(os.path.abspath("code/test.out"))
1395 self.cog.callable_main(["argv0", "-r", "&code/files_to_cog"])
1396 self.assertFilesSame("code/test.cog", "code/test.out")
1398 def run_with_verbosity(self, verbosity):
1399 d = {
1400 "unchanged.cog": """\
1401 //[[[cog
1402 cog.outl("hello world")
1403 //]]]
1404 hello world
1405 //[[[end]]]
1406 """,
1407 "changed.cog": """\
1408 //[[[cog
1409 cog.outl("goodbye cruel world")
1410 //]]]
1411 //[[[end]]]
1412 """,
1413 "cogfiles.txt": """\
1414 unchanged.cog
1415 changed.cog
1416 """,
1417 }
1419 make_files(d)
1420 self.cog.callable_main(
1421 ["argv0", "-r", "--verbosity=" + verbosity, "@cogfiles.txt"]
1422 )
1423 output = self.output.getvalue()
1424 return output
1426 def test_verbosity0(self):
1427 output = self.run_with_verbosity("0")
1428 self.assertEqual(output, "")
1430 def test_verbosity1(self):
1431 output = self.run_with_verbosity("1")
1432 self.assertEqual(output, "Cogging changed.cog (changed)\n")
1434 def test_verbosity2(self):
1435 output = self.run_with_verbosity("2")
1436 self.assertEqual(
1437 output, "Cogging unchanged.cog\nCogging changed.cog (changed)\n"
1438 )
1440 def test_change_dir(self):
1441 # The code can change directories, cog will move us back.
1442 d = {
1443 "sub": {
1444 "data.txt": "Hello!",
1445 },
1446 "test.cog": """\
1447 //[[[cog
1448 import os
1449 os.chdir("sub")
1450 cog.outl(open("data.txt").read())
1451 //]]]
1452 //[[[end]]]
1453 """,
1454 "test.out": """\
1455 //[[[cog
1456 import os
1457 os.chdir("sub")
1458 cog.outl(open("data.txt").read())
1459 //]]]
1460 Hello!
1461 //[[[end]]]
1462 """,
1463 }
1465 make_files(d)
1466 self.cog.callable_main(["argv0", "-r", "test.cog"])
1467 self.assertFilesSame("test.cog", "test.out")
1468 output = self.output.getvalue()
1469 self.assertIn("(changed)", output)
1472class CogTestLineEndings(TestCaseWithTempDir):
1473 """Tests for -U option (force LF line-endings in output)."""
1475 lines_in = [
1476 "Some text.",
1477 "//[[[cog",
1478 'cog.outl("Cog text")',
1479 "//]]]",
1480 "gobbledegook.",
1481 "//[[[end]]]",
1482 "epilogue.",
1483 "",
1484 ]
1486 lines_out = [
1487 "Some text.",
1488 "//[[[cog",
1489 'cog.outl("Cog text")',
1490 "//]]]",
1491 "Cog text",
1492 "//[[[end]]]",
1493 "epilogue.",
1494 "",
1495 ]
1497 def test_output_native_eol(self):
1498 make_files({"infile": "\n".join(self.lines_in)})
1499 self.cog.callable_main(["argv0", "-o", "outfile", "infile"])
1500 self.assertFileContent("outfile", os.linesep.join(self.lines_out))
1502 def test_output_lf_eol(self):
1503 make_files({"infile": "\n".join(self.lines_in)})
1504 self.cog.callable_main(["argv0", "-U", "-o", "outfile", "infile"])
1505 self.assertFileContent("outfile", "\n".join(self.lines_out))
1507 def test_replace_native_eol(self):
1508 make_files({"test.cog": "\n".join(self.lines_in)})
1509 self.cog.callable_main(["argv0", "-r", "test.cog"])
1510 self.assertFileContent("test.cog", os.linesep.join(self.lines_out))
1512 def test_replace_lf_eol(self):
1513 make_files({"test.cog": "\n".join(self.lines_in)})
1514 self.cog.callable_main(["argv0", "-U", "-r", "test.cog"])
1515 self.assertFileContent("test.cog", "\n".join(self.lines_out))
1518class CogTestCharacterEncoding(TestCaseWithTempDir):
1519 def test_simple(self):
1520 d = {
1521 "test.cog": b"""\
1522 // This is my C++ file.
1523 //[[[cog
1524 cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)")
1525 //]]]
1526 //[[[end]]]
1527 """,
1528 "test.out": b"""\
1529 // This is my C++ file.
1530 //[[[cog
1531 cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)")
1532 //]]]
1533 // Unicode: \xe1\x88\xb4 (U+1234)
1534 //[[[end]]]
1535 """.replace(b"\n", os.linesep.encode()),
1536 }
1538 make_files(d)
1539 self.cog.callable_main(["argv0", "-r", "test.cog"])
1540 self.assertFilesSame("test.cog", "test.out")
1541 output = self.output.getvalue()
1542 self.assertIn("(changed)", output)
1544 def test_file_encoding_option(self):
1545 d = {
1546 "test.cog": b"""\
1547 // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows
1548 //[[[cog
1549 cog.outl("\xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe")
1550 //]]]
1551 //[[[end]]]
1552 """,
1553 "test.out": b"""\
1554 // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows
1555 //[[[cog
1556 cog.outl("\xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe")
1557 //]]]
1558 \xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe
1559 //[[[end]]]
1560 """.replace(b"\n", os.linesep.encode()),
1561 }
1563 make_files(d)
1564 self.cog.callable_main(["argv0", "-n", "cp1251", "-r", "test.cog"])
1565 self.assertFilesSame("test.cog", "test.out")
1566 output = self.output.getvalue()
1567 self.assertIn("(changed)", output)
1570class TestCaseWithImports(TestCaseWithTempDir):
1571 """Automatic resetting of sys.modules for tests that import modules.
1573 When running tests which import modules, the sys.modules list
1574 leaks from one test to the next. This test case class scrubs
1575 the list after each run to keep the tests isolated from each other.
1577 """
1579 def setUp(self):
1580 super().setUp()
1581 self.sysmodulekeys = list(sys.modules)
1583 def tearDown(self):
1584 modstoscrub = [
1585 modname for modname in sys.modules if modname not in self.sysmodulekeys
1586 ]
1587 for modname in modstoscrub:
1588 del sys.modules[modname]
1589 super().tearDown()
1592class CogIncludeTests(TestCaseWithImports):
1593 dincludes = {
1594 "test.cog": """\
1595 //[[[cog
1596 import mymodule
1597 //]]]
1598 //[[[end]]]
1599 """,
1600 "test.out": """\
1601 //[[[cog
1602 import mymodule
1603 //]]]
1604 Hello from mymodule
1605 //[[[end]]]
1606 """,
1607 "test2.out": """\
1608 //[[[cog
1609 import mymodule
1610 //]]]
1611 Hello from mymodule in inc2
1612 //[[[end]]]
1613 """,
1614 "include": {
1615 "mymodule.py": """\
1616 import cog
1617 cog.outl("Hello from mymodule")
1618 """
1619 },
1620 "inc2": {
1621 "mymodule.py": """\
1622 import cog
1623 cog.outl("Hello from mymodule in inc2")
1624 """
1625 },
1626 "inc3": {
1627 "someothermodule.py": """\
1628 import cog
1629 cog.outl("This is some other module.")
1630 """
1631 },
1632 }
1634 def test_need_include_path(self):
1635 # Try it without the -I, to see that an ImportError happens.
1636 make_files(self.dincludes)
1637 msg = "(ImportError|ModuleNotFoundError): No module named '?mymodule'?"
1638 with self.assertRaisesRegex(CogUserException, msg):
1639 self.cog.callable_main(["argv0", "-r", "test.cog"])
1641 def test_include_path(self):
1642 # Test that -I adds include directories properly.
1643 make_files(self.dincludes)
1644 self.cog.callable_main(["argv0", "-r", "-I", "include", "test.cog"])
1645 self.assertFilesSame("test.cog", "test.out")
1647 def test_two_include_paths(self):
1648 # Test that two -I's add include directories properly.
1649 make_files(self.dincludes)
1650 self.cog.callable_main(
1651 ["argv0", "-r", "-I", "include", "-I", "inc2", "test.cog"]
1652 )
1653 self.assertFilesSame("test.cog", "test.out")
1655 def test_two_include_paths2(self):
1656 # Test that two -I's add include directories properly.
1657 make_files(self.dincludes)
1658 self.cog.callable_main(
1659 ["argv0", "-r", "-I", "inc2", "-I", "include", "test.cog"]
1660 )
1661 self.assertFilesSame("test.cog", "test2.out")
1663 def test_useless_include_path(self):
1664 # Test that the search will continue past the first directory.
1665 make_files(self.dincludes)
1666 self.cog.callable_main(
1667 ["argv0", "-r", "-I", "inc3", "-I", "include", "test.cog"]
1668 )
1669 self.assertFilesSame("test.cog", "test.out")
1671 def test_sys_path_is_unchanged(self):
1672 d = {
1673 "bad.cog": """\
1674 //[[[cog cog.error("Oh no!") ]]]
1675 //[[[end]]]
1676 """,
1677 "good.cog": """\
1678 //[[[cog cog.outl("Oh yes!") ]]]
1679 //[[[end]]]
1680 """,
1681 }
1683 make_files(d)
1684 # Is it unchanged just by creating a cog engine?
1685 oldsyspath = sys.path[:]
1686 self.new_cog()
1687 self.assertEqual(oldsyspath, sys.path)
1688 # Is it unchanged for a successful run?
1689 self.new_cog()
1690 self.cog.callable_main(["argv0", "-r", "good.cog"])
1691 self.assertEqual(oldsyspath, sys.path)
1692 # Is it unchanged for a successful run with includes?
1693 self.new_cog()
1694 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "good.cog"])
1695 self.assertEqual(oldsyspath, sys.path)
1696 # Is it unchanged for a successful run with two includes?
1697 self.new_cog()
1698 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "-I", "quux", "good.cog"])
1699 self.assertEqual(oldsyspath, sys.path)
1700 # Is it unchanged for a failed run?
1701 self.new_cog()
1702 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1703 self.cog.callable_main(["argv0", "-r", "bad.cog"])
1704 self.assertEqual(oldsyspath, sys.path)
1705 # Is it unchanged for a failed run with includes?
1706 self.new_cog()
1707 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1708 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "bad.cog"])
1709 self.assertEqual(oldsyspath, sys.path)
1710 # Is it unchanged for a failed run with two includes?
1711 self.new_cog()
1712 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1713 self.cog.callable_main(
1714 ["argv0", "-r", "-I", "xyzzy", "-I", "quux", "bad.cog"]
1715 )
1716 self.assertEqual(oldsyspath, sys.path)
1718 def test_sub_directories(self):
1719 # Test that relative paths on the command line work, with includes.
1721 d = {
1722 "code": {
1723 "test.cog": """\
1724 //[[[cog
1725 import mysubmodule
1726 //]]]
1727 //[[[end]]]
1728 """,
1729 "test.out": """\
1730 //[[[cog
1731 import mysubmodule
1732 //]]]
1733 Hello from mysubmodule
1734 //[[[end]]]
1735 """,
1736 "mysubmodule.py": """\
1737 import cog
1738 cog.outl("Hello from mysubmodule")
1739 """,
1740 }
1741 }
1743 make_files(d)
1744 # We should be able to invoke cog without the -I switch, and it will
1745 # auto-include the current directory
1746 self.cog.callable_main(["argv0", "-r", "code/test.cog"])
1747 self.assertFilesSame("code/test.cog", "code/test.out")
1750class CogTestsInFiles(TestCaseWithTempDir):
1751 def test_warn_if_no_cog_code(self):
1752 # Test that the -e switch warns if there is no Cog code.
1753 d = {
1754 "with.cog": """\
1755 //[[[cog
1756 cog.outl("hello world")
1757 //]]]
1758 hello world
1759 //[[[end]]]
1760 """,
1761 "without.cog": """\
1762 There's no cog
1763 code in this file.
1764 """,
1765 }
1767 make_files(d)
1768 self.cog.callable_main(["argv0", "-e", "with.cog"])
1769 output = self.output.getvalue()
1770 self.assertNotIn("Warning", output)
1771 self.new_cog()
1772 self.cog.callable_main(["argv0", "-e", "without.cog"])
1773 output = self.output.getvalue()
1774 self.assertIn("Warning: no cog code found in without.cog", output)
1775 self.new_cog()
1776 self.cog.callable_main(["argv0", "without.cog"])
1777 output = self.output.getvalue()
1778 self.assertNotIn("Warning", output)
1780 def test_file_name_props(self):
1781 d = {
1782 "cog1.txt": """\
1783 //[[[cog
1784 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1785 //]]]
1786 this is cog1.txt in, cog1.txt out
1787 [[[end]]]
1788 """,
1789 "cog1.out": """\
1790 //[[[cog
1791 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1792 //]]]
1793 This is cog1.txt in, cog1.txt out
1794 [[[end]]]
1795 """,
1796 "cog1out.out": """\
1797 //[[[cog
1798 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1799 //]]]
1800 This is cog1.txt in, cog1out.txt out
1801 [[[end]]]
1802 """,
1803 }
1805 make_files(d)
1806 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
1807 self.assertFilesSame("cog1.txt", "cog1.out")
1808 self.new_cog()
1809 self.cog.callable_main(["argv0", "-o", "cog1out.txt", "cog1.txt"])
1810 self.assertFilesSame("cog1out.txt", "cog1out.out")
1812 def test_globals_dont_cross_files(self):
1813 # Make sure that global values don't get shared between files.
1814 d = {
1815 "one.cog": """\
1816 //[[[cog s = "This was set in one.cog" ]]]
1817 //[[[end]]]
1818 //[[[cog cog.outl(s) ]]]
1819 //[[[end]]]
1820 """,
1821 "one.out": """\
1822 //[[[cog s = "This was set in one.cog" ]]]
1823 //[[[end]]]
1824 //[[[cog cog.outl(s) ]]]
1825 This was set in one.cog
1826 //[[[end]]]
1827 """,
1828 "two.cog": """\
1829 //[[[cog
1830 try:
1831 cog.outl(s)
1832 except NameError:
1833 cog.outl("s isn't set!")
1834 //]]]
1835 //[[[end]]]
1836 """,
1837 "two.out": """\
1838 //[[[cog
1839 try:
1840 cog.outl(s)
1841 except NameError:
1842 cog.outl("s isn't set!")
1843 //]]]
1844 s isn't set!
1845 //[[[end]]]
1846 """,
1847 "cogfiles.txt": """\
1848 # Please run cog
1849 one.cog
1851 two.cog
1852 """,
1853 }
1855 make_files(d)
1856 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1857 self.assertFilesSame("one.cog", "one.out")
1858 self.assertFilesSame("two.cog", "two.out")
1859 output = self.output.getvalue()
1860 self.assertIn("(changed)", output)
1862 def test_remove_generated_output(self):
1863 d = {
1864 "cog1.txt": """\
1865 //[[[cog
1866 cog.outl("This line was generated.")
1867 //]]]
1868 This line was generated.
1869 //[[[end]]]
1870 This line was not.
1871 """,
1872 "cog1.out": """\
1873 //[[[cog
1874 cog.outl("This line was generated.")
1875 //]]]
1876 //[[[end]]]
1877 This line was not.
1878 """,
1879 "cog1.out2": """\
1880 //[[[cog
1881 cog.outl("This line was generated.")
1882 //]]]
1883 This line was generated.
1884 //[[[end]]]
1885 This line was not.
1886 """,
1887 }
1889 make_files(d)
1890 # Remove generated output.
1891 self.cog.callable_main(["argv0", "-r", "-x", "cog1.txt"])
1892 self.assertFilesSame("cog1.txt", "cog1.out")
1893 self.new_cog()
1894 # Regenerate the generated output.
1895 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
1896 self.assertFilesSame("cog1.txt", "cog1.out2")
1897 self.new_cog()
1898 # Remove the generated output again.
1899 self.cog.callable_main(["argv0", "-r", "-x", "cog1.txt"])
1900 self.assertFilesSame("cog1.txt", "cog1.out")
1902 def test_msg_call(self):
1903 infile = """\
1904 #[[[cog
1905 cog.msg("Hello there!")
1906 #]]]
1907 #[[[end]]]
1908 """
1909 infile = reindent_block(infile)
1910 self.assertEqual(self.cog.process_string(infile), infile)
1911 output = self.output.getvalue()
1912 self.assertEqual(output, "Message: Hello there!\n")
1914 def test_error_message_has_no_traceback(self):
1915 # Test that a Cog error is printed to stderr with no traceback.
1917 d = {
1918 "cog1.txt": """\
1919 //[[[cog
1920 cog.outl("This line was newly")
1921 cog.outl("generated by cog")
1922 cog.outl("blah blah.")
1923 //]]]
1924 Xhis line was newly
1925 generated by cog
1926 blah blah.
1927 //[[[end]]] (sum: qFQJguWta5)
1928 """,
1929 }
1931 make_files(d)
1932 stderr = io.StringIO()
1933 self.cog.set_output(stderr=stderr)
1934 self.cog.main(["argv0", "-c", "-r", "cog1.txt"])
1935 self.assertEqual(self.output.getvalue(), "Cogging cog1.txt\n")
1936 self.assertEqual(
1937 stderr.getvalue(),
1938 "cog1.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
1939 )
1941 def test_dash_d(self):
1942 d = {
1943 "test.cog": """\
1944 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1945 --[[[end]]]
1946 """,
1947 "test.kablooey": """\
1948 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1949 Defined fooey as kablooey
1950 --[[[end]]]
1951 """,
1952 "test.einstein": """\
1953 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1954 Defined fooey as e=mc2
1955 --[[[end]]]
1956 """,
1957 }
1959 make_files(d)
1960 self.cog.callable_main(["argv0", "-r", "-D", "fooey=kablooey", "test.cog"])
1961 self.assertFilesSame("test.cog", "test.kablooey")
1962 make_files(d)
1963 self.cog.callable_main(["argv0", "-r", "-Dfooey=kablooey", "test.cog"])
1964 self.assertFilesSame("test.cog", "test.kablooey")
1965 make_files(d)
1966 self.cog.callable_main(["argv0", "-r", "-Dfooey=e=mc2", "test.cog"])
1967 self.assertFilesSame("test.cog", "test.einstein")
1968 make_files(d)
1969 self.cog.callable_main(
1970 ["argv0", "-r", "-Dbar=quux", "-Dfooey=kablooey", "test.cog"]
1971 )
1972 self.assertFilesSame("test.cog", "test.kablooey")
1973 make_files(d)
1974 self.cog.callable_main(
1975 ["argv0", "-r", "-Dfooey=kablooey", "-Dbar=quux", "test.cog"]
1976 )
1977 self.assertFilesSame("test.cog", "test.kablooey")
1978 make_files(d)
1979 self.cog.callable_main(
1980 ["argv0", "-r", "-Dfooey=gooey", "-Dfooey=kablooey", "test.cog"]
1981 )
1982 self.assertFilesSame("test.cog", "test.kablooey")
1984 def test_output_to_stdout(self):
1985 d = {
1986 "test.cog": """\
1987 --[[[cog cog.outl('Hey there!') ]]]
1988 --[[[end]]]
1989 """
1990 }
1992 make_files(d)
1993 stderr = io.StringIO()
1994 self.cog.set_output(stderr=stderr)
1995 self.cog.callable_main(["argv0", "test.cog"])
1996 output = self.output.getvalue()
1997 outerr = stderr.getvalue()
1998 self.assertEqual(
1999 output, "--[[[cog cog.outl('Hey there!') ]]]\nHey there!\n--[[[end]]]\n"
2000 )
2001 self.assertEqual(outerr, "")
2003 def test_read_from_stdin(self):
2004 stdin = io.StringIO("--[[[cog cog.outl('Wow') ]]]\n--[[[end]]]\n")
2006 def restore_stdin(old_stdin):
2007 sys.stdin = old_stdin
2009 self.addCleanup(restore_stdin, sys.stdin)
2010 sys.stdin = stdin
2012 stderr = io.StringIO()
2013 self.cog.set_output(stderr=stderr)
2014 self.cog.callable_main(["argv0", "-"])
2015 output = self.output.getvalue()
2016 outerr = stderr.getvalue()
2017 self.assertEqual(output, "--[[[cog cog.outl('Wow') ]]]\nWow\n--[[[end]]]\n")
2018 self.assertEqual(outerr, "")
2020 def test_suffix_output_lines(self):
2021 d = {
2022 "test.cog": """\
2023 Hey there.
2024 ;[[[cog cog.outl('a\\nb\\n \\nc') ]]]
2025 ;[[[end]]]
2026 Good bye.
2027 """,
2028 "test.out": """\
2029 Hey there.
2030 ;[[[cog cog.outl('a\\nb\\n \\nc') ]]]
2031 a (foo)
2032 b (foo)
2033 """ # These three trailing spaces are important.
2034 # The suffix is not applied to completely blank lines.
2035 """
2036 c (foo)
2037 ;[[[end]]]
2038 Good bye.
2039 """,
2040 }
2042 make_files(d)
2043 self.cog.callable_main(["argv0", "-r", "-s", " (foo)", "test.cog"])
2044 self.assertFilesSame("test.cog", "test.out")
2046 def test_empty_suffix(self):
2047 d = {
2048 "test.cog": """\
2049 ;[[[cog cog.outl('a\\nb\\nc') ]]]
2050 ;[[[end]]]
2051 """,
2052 "test.out": """\
2053 ;[[[cog cog.outl('a\\nb\\nc') ]]]
2054 a
2055 b
2056 c
2057 ;[[[end]]]
2058 """,
2059 }
2061 make_files(d)
2062 self.cog.callable_main(["argv0", "-r", "-s", "", "test.cog"])
2063 self.assertFilesSame("test.cog", "test.out")
2065 def test_hellish_suffix(self):
2066 d = {
2067 "test.cog": """\
2068 ;[[[cog cog.outl('a\\n\\nb') ]]]
2069 """,
2070 "test.out": """\
2071 ;[[[cog cog.outl('a\\n\\nb') ]]]
2072 a /\\n*+([)]><
2074 b /\\n*+([)]><
2075 """,
2076 }
2078 make_files(d)
2079 self.cog.callable_main(["argv0", "-z", "-r", "-s", r" /\n*+([)]><", "test.cog"])
2080 self.assertFilesSame("test.cog", "test.out")
2082 def test_prologue(self):
2083 d = {
2084 "test.cog": """\
2085 Some text.
2086 //[[[cog cog.outl(str(math.sqrt(2))[:12])]]]
2087 //[[[end]]]
2088 epilogue.
2089 """,
2090 "test.out": """\
2091 Some text.
2092 //[[[cog cog.outl(str(math.sqrt(2))[:12])]]]
2093 1.4142135623
2094 //[[[end]]]
2095 epilogue.
2096 """,
2097 }
2099 make_files(d)
2100 self.cog.callable_main(["argv0", "-r", "-p", "import math", "test.cog"])
2101 self.assertFilesSame("test.cog", "test.out")
2103 def test_threads(self):
2104 # Test that the implicitly imported cog module is actually different for
2105 # different threads.
2106 numthreads = 20
2108 d = {}
2109 for i in range(numthreads):
2110 d[f"f{i}.cog"] = (
2111 "x\n" * i
2112 + "[[[cog\n"
2113 + f"assert cog.firstLineNum == int(FIRST) == {i + 1}\n"
2114 + "]]]\n"
2115 + "[[[end]]]\n"
2116 )
2117 make_files(d)
2119 results = []
2121 def thread_main(num):
2122 try:
2123 ret = Cog().main(
2124 ["cog.py", "-r", "-D", f"FIRST={num + 1}", f"f{num}.cog"]
2125 )
2126 assert ret == 0
2127 except Exception as exc: # pragma: no cover (only happens on test failure)
2128 results.append(exc)
2129 else:
2130 results.append(None)
2132 ts = [
2133 threading.Thread(target=thread_main, args=(i,)) for i in range(numthreads)
2134 ]
2135 for t in ts:
2136 t.start()
2137 for t in ts:
2138 t.join()
2139 assert results == [None] * numthreads
2142class CheckTests(TestCaseWithTempDir):
2143 def run_check(self, args, status=0):
2144 actual_status = self.cog.main(["argv0", "--check"] + args)
2145 print(self.output.getvalue())
2146 self.assertEqual(status, actual_status)
2148 def assert_made_files_unchanged(self, d):
2149 for name, content in d.items():
2150 content = reindent_block(content)
2151 if os.name == "nt":
2152 content = content.replace("\n", "\r\n")
2153 self.assertFileContent(name, content)
2155 def test_check_no_cog(self):
2156 d = {
2157 "hello.txt": """\
2158 Hello.
2159 """,
2160 }
2161 make_files(d)
2162 self.run_check(["hello.txt"], status=0)
2163 self.assertEqual(self.output.getvalue(), "Checking hello.txt\n")
2164 self.assert_made_files_unchanged(d)
2166 def test_check_good(self):
2167 d = {
2168 "unchanged.cog": """\
2169 //[[[cog
2170 cog.outl("hello world")
2171 //]]]
2172 hello world
2173 //[[[end]]]
2174 """,
2175 }
2176 make_files(d)
2177 self.run_check(["unchanged.cog"], status=0)
2178 self.assertEqual(self.output.getvalue(), "Checking unchanged.cog\n")
2179 self.assert_made_files_unchanged(d)
2181 def test_check_bad(self):
2182 d = {
2183 "changed.cog": """\
2184 //[[[cog
2185 cog.outl("goodbye world")
2186 //]]]
2187 hello world
2188 //[[[end]]]
2189 """,
2190 }
2191 make_files(d)
2192 self.run_check(["changed.cog"], status=5)
2193 self.assertEqual(
2194 self.output.getvalue(), "Checking changed.cog (changed)\nCheck failed\n"
2195 )
2196 self.assert_made_files_unchanged(d)
2198 def test_check_bad_with_diff(self):
2199 d = {
2200 "skittering.cog": """\
2201 //[[[cog
2202 for i in range(5): cog.outl(f"number {i}")
2203 cog.outl("goodbye world")
2204 //]]]
2205 number 0
2206 number 1
2207 number 2
2208 number 3
2209 number 4
2210 hello world
2211 //[[[end]]]
2212 """,
2213 }
2214 make_files(d)
2215 self.run_check(["--diff", "skittering.cog"], status=5)
2216 output = """\
2217 Checking skittering.cog (changed)
2218 --- current skittering.cog
2219 +++ changed skittering.cog
2220 @@ -7,5 +7,5 @@
2221 number 2
2222 number 3
2223 number 4
2224 -hello world
2225 +goodbye world
2226 //[[[end]]]
2227 Check failed
2228 """
2229 self.assertEqual(self.output.getvalue(), reindent_block(output))
2230 self.assert_made_files_unchanged(d)
2232 def test_check_bad_with_message(self):
2233 d = {
2234 "changed.cog": """\
2235 //[[[cog
2236 cog.outl("goodbye world")
2237 //]]]
2238 hello world
2239 //[[[end]]]
2240 """,
2241 }
2242 make_files(d)
2243 self.run_check(
2244 ["--check-fail-msg=Run `make cogged` to fix", "changed.cog"], status=5
2245 )
2246 self.assertEqual(
2247 self.output.getvalue(),
2248 "Checking changed.cog (changed)\nCheck failed: Run `make cogged` to fix\n",
2249 )
2250 self.assert_made_files_unchanged(d)
2252 def test_check_mixed(self):
2253 d = {
2254 "unchanged.cog": """\
2255 //[[[cog
2256 cog.outl("hello world")
2257 //]]]
2258 hello world
2259 //[[[end]]]
2260 """,
2261 "changed.cog": """\
2262 //[[[cog
2263 cog.outl("goodbye world")
2264 //]]]
2265 hello world
2266 //[[[end]]]
2267 """,
2268 }
2269 make_files(d)
2270 for verbosity, output in [
2271 ("0", "Check failed\n"),
2272 ("1", "Checking changed.cog (changed)\nCheck failed\n"),
2273 (
2274 "2",
2275 "Checking unchanged.cog\nChecking changed.cog (changed)\nCheck failed\n",
2276 ),
2277 ]:
2278 self.new_cog()
2279 self.run_check(
2280 ["--verbosity=%s" % verbosity, "unchanged.cog", "changed.cog"], status=5
2281 )
2282 self.assertEqual(self.output.getvalue(), output)
2283 self.assert_made_files_unchanged(d)
2285 def test_check_with_good_checksum(self):
2286 d = {
2287 "good.txt": """\
2288 //[[[cog
2289 cog.outl("This line was newly")
2290 cog.outl("generated by cog")
2291 cog.outl("blah blah.")
2292 //]]]
2293 This line was newly
2294 generated by cog
2295 blah blah.
2296 //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346)
2297 """,
2298 }
2299 make_files(d)
2300 # Have to use -c with --check if there are checksums in the file.
2301 self.run_check(["-c", "good.txt"], status=0)
2302 self.assertEqual(self.output.getvalue(), "Checking good.txt\n")
2303 self.assert_made_files_unchanged(d)
2305 def test_check_with_bad_checksum(self):
2306 d = {
2307 "bad.txt": """\
2308 //[[[cog
2309 cog.outl("This line was newly")
2310 cog.outl("generated by cog")
2311 cog.outl("blah blah.")
2312 //]]]
2313 This line was newly
2314 generated by cog
2315 blah blah.
2316 //[[[end]]] (checksum: a9999999e5ad6b95c9e9a184b26f4346)
2317 """,
2318 }
2319 make_files(d)
2320 # Have to use -c with --check if there are checksums in the file.
2321 self.run_check(["-c", "bad.txt"], status=1)
2322 self.assertEqual(
2323 self.output.getvalue(),
2324 "Checking bad.txt\nbad.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
2325 )
2326 self.assert_made_files_unchanged(d)
2328 def test_check_with_good_sum(self):
2329 d = {
2330 "good.txt": """\
2331 //[[[cog
2332 cog.outl("This line was newly")
2333 cog.outl("generated by cog")
2334 cog.outl("blah blah.")
2335 //]]]
2336 This line was newly
2337 generated by cog
2338 blah blah.
2339 //[[[end]]] (sum: qFQJguWta5)
2340 """,
2341 }
2342 make_files(d)
2343 # Have to use -c with --check if there are checksums in the file.
2344 self.run_check(["-c", "good.txt"], status=0)
2345 self.assertEqual(self.output.getvalue(), "Checking good.txt\n")
2346 self.assert_made_files_unchanged(d)
2348 def test_check_with_bad_sum(self):
2349 d = {
2350 "bad.txt": """\
2351 //[[[cog
2352 cog.outl("This line was newly")
2353 cog.outl("generated by cog")
2354 cog.outl("blah blah.")
2355 //]]]
2356 This line was newly
2357 generated by cog
2358 blah blah.
2359 //[[[end]]] (sum: qZmZmeWta5)
2360 """,
2361 }
2362 make_files(d)
2363 # Have to use -c with --check if there are checksums in the file.
2364 self.run_check(["-c", "bad.txt"], status=1)
2365 self.assertEqual(
2366 self.output.getvalue(),
2367 "Checking bad.txt\nbad.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
2368 )
2369 self.assert_made_files_unchanged(d)
2372class WritabilityTests(TestCaseWithTempDir):
2373 d = {
2374 "test.cog": """\
2375 //[[[cog
2376 for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']:
2377 cog.outl("void %s();" % fn)
2378 //]]]
2379 //[[[end]]]
2380 """,
2381 "test.out": """\
2382 //[[[cog
2383 for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']:
2384 cog.outl("void %s();" % fn)
2385 //]]]
2386 void DoSomething();
2387 void DoAnotherThing();
2388 void DoLastThing();
2389 //[[[end]]]
2390 """,
2391 }
2393 if os.name == "nt": 2393 ↛ 2395line 2393 didn't jump to line 2395 because the condition on line 2393 was never true
2394 # for Windows
2395 cmd_w_args = "attrib -R %s"
2396 cmd_w_asterisk = "attrib -R *"
2397 else:
2398 # for unix-like
2399 cmd_w_args = "chmod +w %s"
2400 cmd_w_asterisk = "chmod +w *"
2402 def setUp(self):
2403 super().setUp()
2404 make_files(self.d)
2405 self.testcog = os.path.join(self.tempdir, "test.cog")
2406 os.chmod(self.testcog, stat.S_IREAD) # Make the file readonly.
2407 assert not os.access(self.testcog, os.W_OK)
2409 def tearDown(self):
2410 os.chmod(self.testcog, stat.S_IWRITE) # Make the file writable again.
2411 super().tearDown()
2413 def test_readonly_no_command(self):
2414 with self.assertRaisesRegex(CogError, "^Can't overwrite test.cog$"):
2415 self.cog.callable_main(["argv0", "-r", "test.cog"])
2416 assert not os.access(self.testcog, os.W_OK)
2418 def test_readonly_with_command(self):
2419 self.cog.callable_main(["argv0", "-r", "-w", self.cmd_w_args, "test.cog"])
2420 self.assertFilesSame("test.cog", "test.out")
2421 assert os.access(self.testcog, os.W_OK)
2423 def test_readonly_with_command_with_no_slot(self):
2424 self.cog.callable_main(["argv0", "-r", "-w", self.cmd_w_asterisk, "test.cog"])
2425 self.assertFilesSame("test.cog", "test.out")
2426 assert os.access(self.testcog, os.W_OK)
2428 def test_readonly_with_ineffectual_command(self):
2429 with self.assertRaisesRegex(CogError, "^Couldn't make test.cog writable$"):
2430 self.cog.callable_main(["argv0", "-r", "-w", "echo %s", "test.cog"])
2431 assert not os.access(self.testcog, os.W_OK)
2434class ChecksumTests(TestCaseWithTempDir):
2435 def test_create_checksum_output(self):
2436 d = {
2437 "cog1.txt": """\
2438 //[[[cog
2439 cog.outl("This line was generated.")
2440 //]]]
2441 This line was generated.
2442 //[[[end]]] what
2443 This line was not.
2444 """,
2445 "cog1.out": """\
2446 //[[[cog
2447 cog.outl("This line was generated.")
2448 //]]]
2449 This line was generated.
2450 //[[[end]]] (sum: itsT+1m5lq) what
2451 This line was not.
2452 """,
2453 }
2455 make_files(d)
2456 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2457 self.assertFilesSame("cog1.txt", "cog1.out")
2459 def test_check_checksum_output(self):
2460 d = {
2461 "cog1.txt": """\
2462 //[[[cog
2463 cog.outl("This line was newly")
2464 cog.outl("generated by cog")
2465 cog.outl("blah blah.")
2466 //]]]
2467 This line was generated.
2468 //[[[end]]] (sum: itsT+1m5lq) end
2469 """,
2470 "cog1.out": """\
2471 //[[[cog
2472 cog.outl("This line was newly")
2473 cog.outl("generated by cog")
2474 cog.outl("blah blah.")
2475 //]]]
2476 This line was newly
2477 generated by cog
2478 blah blah.
2479 //[[[end]]] (sum: qFQJguWta5) end
2480 """,
2481 }
2483 make_files(d)
2484 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2485 self.assertFilesSame("cog1.txt", "cog1.out")
2487 def test_check_old_checksum_format(self):
2488 # Test that old checksum format can still be read
2489 d = {
2490 "cog1.txt": """\
2491 //[[[cog
2492 cog.outl("This line was newly")
2493 cog.outl("generated by cog")
2494 cog.outl("blah blah.")
2495 //]]]
2496 This line was generated.
2497 //[[[end]]] (checksum: 8adb13fb59b996a1c7f0065ea9f3d893) end
2498 """,
2499 "cog1.out": """\
2500 //[[[cog
2501 cog.outl("This line was newly")
2502 cog.outl("generated by cog")
2503 cog.outl("blah blah.")
2504 //]]]
2505 This line was newly
2506 generated by cog
2507 blah blah.
2508 //[[[end]]] (sum: qFQJguWta5) end
2509 """,
2510 }
2512 make_files(d)
2513 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2514 self.assertFilesSame("cog1.txt", "cog1.out")
2516 def test_remove_checksum_output(self):
2517 d = {
2518 "cog1.txt": """\
2519 //[[[cog
2520 cog.outl("This line was newly")
2521 cog.outl("generated by cog")
2522 cog.outl("blah blah.")
2523 //]]]
2524 This line was generated.
2525 //[[[end]]] (sum: itsT+1m5lq) fooey
2526 """,
2527 "cog1.out": """\
2528 //[[[cog
2529 cog.outl("This line was newly")
2530 cog.outl("generated by cog")
2531 cog.outl("blah blah.")
2532 //]]]
2533 This line was newly
2534 generated by cog
2535 blah blah.
2536 //[[[end]]] fooey
2537 """,
2538 }
2540 make_files(d)
2541 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
2542 self.assertFilesSame("cog1.txt", "cog1.out")
2544 def test_tampered_checksum_output(self):
2545 d = {
2546 "cog1.txt": """\
2547 //[[[cog
2548 cog.outl("This line was newly")
2549 cog.outl("generated by cog")
2550 cog.outl("blah blah.")
2551 //]]]
2552 Xhis line was newly
2553 generated by cog
2554 blah blah.
2555 //[[[end]]] (sum: qFQJguWta5)
2556 """,
2557 "cog2.txt": """\
2558 //[[[cog
2559 cog.outl("This line was newly")
2560 cog.outl("generated by cog")
2561 cog.outl("blah blah.")
2562 //]]]
2563 This line was newly
2564 generated by cog
2565 blah blah!
2566 //[[[end]]] (sum: qFQJguWta5)
2567 """,
2568 "cog3.txt": """\
2569 //[[[cog
2570 cog.outl("This line was newly")
2571 cog.outl("generated by cog")
2572 cog.outl("blah blah.")
2573 //]]]
2575 This line was newly
2576 generated by cog
2577 blah blah.
2578 //[[[end]]] (sum: qFQJguWta5)
2579 """,
2580 "cog4.txt": """\
2581 //[[[cog
2582 cog.outl("This line was newly")
2583 cog.outl("generated by cog")
2584 cog.outl("blah blah.")
2585 //]]]
2586 This line was newly
2587 generated by cog
2588 blah blah..
2589 //[[[end]]] (sum: qFQJguWta5)
2590 """,
2591 "cog5.txt": """\
2592 //[[[cog
2593 cog.outl("This line was newly")
2594 cog.outl("generated by cog")
2595 cog.outl("blah blah.")
2596 //]]]
2597 This line was newly
2598 generated by cog
2599 blah blah.
2600 extra
2601 //[[[end]]] (sum: qFQJguWta5)
2602 """,
2603 "cog6.txt": """\
2604 //[[[cog
2605 cog.outl("This line was newly")
2606 cog.outl("generated by cog")
2607 cog.outl("blah blah.")
2608 //]]]
2609 //[[[end]]] (sum: qFQJguWta5)
2610 """,
2611 }
2613 make_files(d)
2614 with self.assertRaisesRegex(
2615 CogError,
2616 r"^cog1.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2617 ):
2618 self.cog.callable_main(["argv0", "-c", "cog1.txt"])
2619 with self.assertRaisesRegex(
2620 CogError,
2621 r"^cog2.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2622 ):
2623 self.cog.callable_main(["argv0", "-c", "cog2.txt"])
2624 with self.assertRaisesRegex(
2625 CogError,
2626 r"^cog3.txt\(10\): Output has been edited! Delete old checksum to unprotect.$",
2627 ):
2628 self.cog.callable_main(["argv0", "-c", "cog3.txt"])
2629 with self.assertRaisesRegex(
2630 CogError,
2631 r"^cog4.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2632 ):
2633 self.cog.callable_main(["argv0", "-c", "cog4.txt"])
2634 with self.assertRaisesRegex(
2635 CogError,
2636 r"^cog5.txt\(10\): Output has been edited! Delete old checksum to unprotect.$",
2637 ):
2638 self.cog.callable_main(["argv0", "-c", "cog5.txt"])
2639 with self.assertRaisesRegex(
2640 CogError,
2641 r"^cog6.txt\(6\): Output has been edited! Delete old checksum to unprotect.$",
2642 ):
2643 self.cog.callable_main(["argv0", "-c", "cog6.txt"])
2645 def test_argv_isnt_modified(self):
2646 argv = ["argv0", "-v"]
2647 orig_argv = argv[:]
2648 self.cog.callable_main(argv)
2649 self.assertEqual(argv, orig_argv)
2652class CustomMarkerTests(TestCaseWithTempDir):
2653 def test_customer_markers(self):
2654 d = {
2655 "test.cog": """\
2656 //{{
2657 cog.outl("void %s();" % "MyFunction")
2658 //}}
2659 //{{end}}
2660 """,
2661 "test.out": """\
2662 //{{
2663 cog.outl("void %s();" % "MyFunction")
2664 //}}
2665 void MyFunction();
2666 //{{end}}
2667 """,
2668 }
2670 make_files(d)
2671 self.cog.callable_main(["argv0", "-r", "--markers={{ }} {{end}}", "test.cog"])
2672 self.assertFilesSame("test.cog", "test.out")
2674 def test_truly_wacky_markers(self):
2675 # Make sure the markers are properly re-escaped.
2676 d = {
2677 "test.cog": """\
2678 //**(
2679 cog.outl("void %s();" % "MyFunction")
2680 //**)
2681 //**(end)**
2682 """,
2683 "test.out": """\
2684 //**(
2685 cog.outl("void %s();" % "MyFunction")
2686 //**)
2687 void MyFunction();
2688 //**(end)**
2689 """,
2690 }
2692 make_files(d)
2693 self.cog.callable_main(
2694 ["argv0", "-r", "--markers=**( **) **(end)**", "test.cog"]
2695 )
2696 self.assertFilesSame("test.cog", "test.out")
2698 def test_change_just_one_marker(self):
2699 d = {
2700 "test.cog": """\
2701 //**(
2702 cog.outl("void %s();" % "MyFunction")
2703 //]]]
2704 //[[[end]]]
2705 """,
2706 "test.out": """\
2707 //**(
2708 cog.outl("void %s();" % "MyFunction")
2709 //]]]
2710 void MyFunction();
2711 //[[[end]]]
2712 """,
2713 }
2715 make_files(d)
2716 self.cog.callable_main(
2717 ["argv0", "-r", "--markers=**( ]]] [[[end]]]", "test.cog"]
2718 )
2719 self.assertFilesSame("test.cog", "test.out")
2722class BlakeTests(TestCaseWithTempDir):
2723 # Blake Winton's contributions.
2724 def test_delete_code(self):
2725 # -o sets the output file.
2726 d = {
2727 "test.cog": """\
2728 // This is my C++ file.
2729 //[[[cog
2730 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
2731 for fn in fnames:
2732 cog.outl("void %s();" % fn)
2733 //]]]
2734 Some Sample Code Here
2735 //[[[end]]]Data Data
2736 And Some More
2737 """,
2738 "test.out": """\
2739 // This is my C++ file.
2740 void DoSomething();
2741 void DoAnotherThing();
2742 void DoLastThing();
2743 And Some More
2744 """,
2745 }
2747 make_files(d)
2748 self.cog.callable_main(["argv0", "-d", "-o", "test.cogged", "test.cog"])
2749 self.assertFilesSame("test.cogged", "test.out")
2751 def test_delete_code_with_dash_r_fails(self):
2752 d = {
2753 "test.cog": """\
2754 // This is my C++ file.
2755 """
2756 }
2758 make_files(d)
2759 with self.assertRaisesRegex(
2760 CogUsageError,
2761 r"^Can't use -d with -r \(or you would delete all your source!\)$",
2762 ):
2763 self.cog.callable_main(["argv0", "-r", "-d", "test.cog"])
2765 def test_setting_globals(self):
2766 # Blake Winton contributed a way to set the globals that will be used in
2767 # processFile().
2768 d = {
2769 "test.cog": """\
2770 // This is my C++ file.
2771 //[[[cog
2772 for fn in fnames:
2773 cog.outl("void %s();" % fn)
2774 //]]]
2775 Some Sample Code Here
2776 //[[[end]]]""",
2777 "test.out": """\
2778 // This is my C++ file.
2779 void DoBlake();
2780 void DoWinton();
2781 void DoContribution();
2782 """,
2783 }
2785 make_files(d)
2786 globals = {}
2787 globals["fnames"] = ["DoBlake", "DoWinton", "DoContribution"]
2788 self.cog.options.delete_code = True
2789 self.cog.process_file("test.cog", "test.cogged", globals=globals)
2790 self.assertFilesSame("test.cogged", "test.out")
2793class ErrorCallTests(TestCaseWithTempDir):
2794 def test_error_call_has_no_traceback(self):
2795 # Test that cog.error() doesn't show a traceback.
2796 d = {
2797 "error.cog": """\
2798 //[[[cog
2799 cog.error("Something Bad!")
2800 //]]]
2801 //[[[end]]]
2802 """,
2803 }
2805 make_files(d)
2806 self.cog.main(["argv0", "-r", "error.cog"])
2807 output = self.output.getvalue()
2808 self.assertEqual(output, "Cogging error.cog\nError: Something Bad!\n")
2810 def test_real_error_has_traceback(self):
2811 # Test that a genuine error does show a traceback.
2812 d = {
2813 "error.cog": """\
2814 //[[[cog
2815 raise RuntimeError("Hey!")
2816 //]]]
2817 //[[[end]]]
2818 """,
2819 }
2821 make_files(d)
2822 self.cog.main(["argv0", "-r", "error.cog"])
2823 output = self.output.getvalue()
2824 msg = "Actual output:\n" + output
2825 self.assertTrue(
2826 output.startswith("Cogging error.cog\nTraceback (most recent"), msg
2827 )
2828 self.assertIn("RuntimeError: Hey!", output)
2831class HashHandlerTests(TestCase):
2832 """Test cases for HashHandler functionality."""
2834 def setUp(self):
2835 self.handler = HashHandler("[[[end]]]")
2837 def test_validate_hash_with_base64_mismatch(self):
2838 # Test the base64 validation branch with a mismatch
2839 line = "//[[[end]]] (sum: wronghas12)" # 10 chars to match regex
2840 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346"
2842 with self.assertRaises(ValueError) as cm:
2843 self.handler.validate_hash(line, expected_hash)
2844 self.assertEqual(
2845 str(cm.exception),
2846 "Output has been edited! Delete old checksum to unprotect.",
2847 )
2849 def test_validate_hash_with_base64_match(self):
2850 # Test the base64 validation branch with a match
2851 line = "//[[[end]]] (sum: qFQJguWta5)"
2852 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346"
2854 # Should not raise an exception
2855 result = self.handler.validate_hash(line, expected_hash)
2856 self.assertTrue(result)
2859# Things not yet tested:
2860# - A bad -w command (currently fails silently).