Coverage for cogapp / test_cogapp.py: 99.36%
912 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-25 07:14 -0500
« prev ^ index » next coverage.py v7.13.2, created at 2026-01-25 07:14 -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))
471class CogOptionsTests(TestCase):
472 """Test the CogOptions class."""
474 def test_equality(self):
475 o = CogOptions()
476 p = CogOptions()
477 self.assertEqual(o, p)
478 o.parse_args(["-r"])
479 self.assertNotEqual(o, p)
480 p.parse_args(["-r"])
481 self.assertEqual(o, p)
483 def test_cloning(self):
484 o = CogOptions()
485 o.parse_args(["-I", "fooey", "-I", "booey", "-s", " /*x*/"])
486 p = o.clone()
487 self.assertEqual(o, p)
488 p.parse_args(["-I", "huey", "-D", "foo=quux"])
489 self.assertNotEqual(o, p)
490 q = CogOptions()
491 q.parse_args(
492 [
493 "-I",
494 "fooey",
495 "-I",
496 "booey",
497 "-s",
498 " /*x*/",
499 "-I",
500 "huey",
501 "-D",
502 "foo=quux",
503 ]
504 )
505 self.assertEqual(p, q)
507 def test_combining_flags(self):
508 # Single-character flags can be combined.
509 o = CogOptions()
510 o.parse_args(["-e", "-r", "-z"])
511 p = CogOptions()
512 p.parse_args(["-erz"])
513 self.assertEqual(o, p)
515 def test_markers(self):
516 o = Markers.from_arg("a b c")
517 self.assertEqual("a", o.begin_spec)
518 self.assertEqual("b", o.end_spec)
519 self.assertEqual("c", o.end_output)
521 def test_markers_switch(self):
522 o = CogOptions()
523 o.parse_args(["--markers", "a b c"])
524 self.assertEqual("a", o.begin_spec)
525 self.assertEqual("b", o.end_spec)
526 self.assertEqual("c", o.end_output)
529class FileStructureTests(TestCase):
530 """Test that we're properly strict about the structure of files."""
532 def is_bad(self, infile, msg=None):
533 infile = reindent_block(infile)
534 with self.assertRaisesRegex(CogError, "^" + re.escape(msg) + "$"):
535 Cog().process_string(infile, "infile.txt")
537 def test_begin_no_end(self):
538 infile = """\
539 Fooey
540 #[[[cog
541 cog.outl('hello')
542 """
543 self.is_bad(infile, "infile.txt(2): Cog block begun but never ended.")
545 def test_no_eoo(self):
546 infile = """\
547 Fooey
548 #[[[cog
549 cog.outl('hello')
550 #]]]
551 """
552 self.is_bad(infile, "infile.txt(4): Missing '[[[end]]]' before end of file.")
554 infile2 = """\
555 Fooey
556 #[[[cog
557 cog.outl('hello')
558 #]]]
559 #[[[cog
560 cog.outl('goodbye')
561 #]]]
562 """
563 self.is_bad(infile2, "infile.txt(5): Unexpected '[[[cog'")
565 def test_start_with_end(self):
566 infile = """\
567 #]]]
568 """
569 self.is_bad(infile, "infile.txt(1): Unexpected ']]]'")
571 infile2 = """\
572 #[[[cog
573 cog.outl('hello')
574 #]]]
575 #[[[end]]]
576 #]]]
577 """
578 self.is_bad(infile2, "infile.txt(5): Unexpected ']]]'")
580 def test_start_with_eoo(self):
581 infile = """\
582 #[[[end]]]
583 """
584 self.is_bad(infile, "infile.txt(1): Unexpected '[[[end]]]'")
586 infile2 = """\
587 #[[[cog
588 cog.outl('hello')
589 #]]]
590 #[[[end]]]
591 #[[[end]]]
592 """
593 self.is_bad(infile2, "infile.txt(5): Unexpected '[[[end]]]'")
595 def test_no_end(self):
596 infile = """\
597 #[[[cog
598 cog.outl("hello")
599 #[[[end]]]
600 """
601 self.is_bad(infile, "infile.txt(3): Unexpected '[[[end]]]'")
603 infile2 = """\
604 #[[[cog
605 cog.outl('hello')
606 #]]]
607 #[[[end]]]
608 #[[[cog
609 cog.outl("hello")
610 #[[[end]]]
611 """
612 self.is_bad(infile2, "infile.txt(7): Unexpected '[[[end]]]'")
614 def test_two_begins(self):
615 infile = """\
616 #[[[cog
617 #[[[cog
618 cog.outl("hello")
619 #]]]
620 #[[[end]]]
621 """
622 self.is_bad(infile, "infile.txt(2): Unexpected '[[[cog'")
624 infile2 = """\
625 #[[[cog
626 cog.outl("hello")
627 #]]]
628 #[[[end]]]
629 #[[[cog
630 #[[[cog
631 cog.outl("hello")
632 #]]]
633 #[[[end]]]
634 """
635 self.is_bad(infile2, "infile.txt(6): Unexpected '[[[cog'")
637 def test_two_ends(self):
638 infile = """\
639 #[[[cog
640 cog.outl("hello")
641 #]]]
642 #]]]
643 #[[[end]]]
644 """
645 self.is_bad(infile, "infile.txt(4): Unexpected ']]]'")
647 infile2 = """\
648 #[[[cog
649 cog.outl("hello")
650 #]]]
651 #[[[end]]]
652 #[[[cog
653 cog.outl("hello")
654 #]]]
655 #]]]
656 #[[[end]]]
657 """
658 self.is_bad(infile2, "infile.txt(8): Unexpected ']]]'")
661class CogErrorTests(TestCase):
662 """Test cases for cog.error()."""
664 def test_error_msg(self):
665 infile = """\
666 [[[cog cog.error("This ain't right!")]]]
667 [[[end]]]
668 """
670 infile = reindent_block(infile)
671 with self.assertRaisesRegex(CogGeneratedError, "^This ain't right!$"):
672 Cog().process_string(infile)
674 def test_error_no_msg(self):
675 infile = """\
676 [[[cog cog.error()]]]
677 [[[end]]]
678 """
680 infile = reindent_block(infile)
681 with self.assertRaisesRegex(
682 CogGeneratedError, "^Error raised by cog generator.$"
683 ):
684 Cog().process_string(infile)
686 def test_no_error_if_error_not_called(self):
687 infile = """\
688 --[[[cog
689 --import cog
690 --for i in range(3):
691 -- if i > 10:
692 -- cog.error("Something is amiss!")
693 -- cog.out("xx%d\\n" % i)
694 --]]]
695 xx0
696 xx1
697 xx2
698 --[[[end]]]
699 """
701 infile = reindent_block(infile)
702 self.assertEqual(Cog().process_string(infile), infile)
705class CogGeneratorGetCodeTests(TestCase):
706 """Tests for CogGenerator.getCode()."""
708 def setUp(self):
709 # All tests get a generator to use, and short same-length names for
710 # the functions we're going to use.
711 self.gen = CogGenerator()
712 self.m = self.gen.parse_marker
713 self.parse_line = self.gen.parse_line
715 def test_empty(self):
716 self.m("// [[[cog")
717 self.m("// ]]]")
718 self.assertEqual(self.gen.get_code(), "")
720 def test_simple(self):
721 self.m("// [[[cog")
722 self.parse_line(' print "hello"')
723 self.parse_line(' print "bye"')
724 self.m("// ]]]")
725 self.assertEqual(self.gen.get_code(), 'print "hello"\nprint "bye"')
727 def test_compressed1(self):
728 # For a while, I supported compressed code blocks, but no longer.
729 self.m('// [[[cog: print """')
730 self.parse_line("// hello")
731 self.parse_line("// bye")
732 self.m('// """)]]]')
733 self.assertEqual(self.gen.get_code(), "hello\nbye")
735 def test_compressed2(self):
736 # For a while, I supported compressed code blocks, but no longer.
737 self.m('// [[[cog: print """')
738 self.parse_line("hello")
739 self.parse_line("bye")
740 self.m('// """)]]]')
741 self.assertEqual(self.gen.get_code(), "hello\nbye")
743 def test_compressed3(self):
744 # For a while, I supported compressed code blocks, but no longer.
745 self.m("// [[[cog")
746 self.parse_line('print """hello')
747 self.parse_line("bye")
748 self.m('// """)]]]')
749 self.assertEqual(self.gen.get_code(), 'print """hello\nbye')
751 def test_compressed4(self):
752 # For a while, I supported compressed code blocks, but no longer.
753 self.m('// [[[cog: print """')
754 self.parse_line("hello")
755 self.parse_line('bye""")')
756 self.m("// ]]]")
757 self.assertEqual(self.gen.get_code(), 'hello\nbye""")')
759 def test_no_common_prefix_for_markers(self):
760 # It's important to be able to use #if 0 to hide lines from a
761 # C++ compiler.
762 self.m("#if 0 //[[[cog")
763 self.parse_line("\timport cog, sys")
764 self.parse_line("")
765 self.parse_line("\tprint sys.argv")
766 self.m("#endif //]]]")
767 self.assertEqual(self.gen.get_code(), "import cog, sys\n\nprint sys.argv")
770class TestCaseWithTempDir(TestCase):
771 def new_cog(self):
772 """Initialize the cog members for another run."""
773 # Create a cog engine, and catch its output.
774 self.cog = Cog()
775 self.output = io.StringIO()
776 self.cog.set_output(stdout=self.output, stderr=self.output)
778 def setUp(self):
779 # Create a temporary directory.
780 self.tempdir = os.path.join(
781 tempfile.gettempdir(), "testcog_tempdir_" + str(random.random())[2:]
782 )
783 os.mkdir(self.tempdir)
784 self.olddir = os.getcwd()
785 os.chdir(self.tempdir)
786 self.new_cog()
788 def tearDown(self):
789 os.chdir(self.olddir)
790 # Get rid of the temporary directory.
791 shutil.rmtree(self.tempdir)
793 def assertFilesSame(self, file_name1, file_name2):
794 with open(os.path.join(self.tempdir, file_name1), "rb") as f1:
795 text1 = f1.read()
796 with open(os.path.join(self.tempdir, file_name2), "rb") as f2:
797 text2 = f2.read()
798 self.assertEqual(text1, text2)
800 def assertFileContent(self, fname, content):
801 absname = os.path.join(self.tempdir, fname)
802 with open(absname, "rb") as f:
803 file_content = f.read()
804 self.assertEqual(file_content, content.encode("utf-8"))
807class ArgumentHandlingTests(TestCaseWithTempDir):
808 def test_argument_failure(self):
809 # Return value 2 means usage problem.
810 self.assertEqual(self.cog.main(["argv0", "-j"]), 2)
811 output = self.output.getvalue()
812 self.assertIn("unrecognized arguments: -j", output)
813 with self.assertRaisesRegex(CogUsageError, r"^No files to process$"):
814 self.cog.callable_main(["argv0"])
815 with self.assertRaisesRegex(CogUsageError, r"^unrecognized arguments: -j$"):
816 self.cog.callable_main(["argv0", "-j"])
818 def test_no_dash_o_and_at_file(self):
819 make_files({"cogfiles.txt": "# Please run cog"})
820 with self.assertRaisesRegex(CogUsageError, r"^Can't use -o with @file$"):
821 self.cog.callable_main(["argv0", "-o", "foo", "@cogfiles.txt"])
823 def test_no_dash_o_and_amp_file(self):
824 make_files({"cogfiles.txt": "# Please run cog"})
825 with self.assertRaisesRegex(CogUsageError, r"^Can't use -o with &file$"):
826 self.cog.callable_main(["argv0", "-o", "foo", "&cogfiles.txt"])
828 def test_no_diff_without_check(self):
829 with self.assertRaisesRegex(
830 CogUsageError, r"^Can't use --diff without --check$"
831 ):
832 self.cog.callable_main(["argv0", "--diff"])
834 def test_dash_v(self):
835 self.assertEqual(self.cog.main(["argv0", "-v"]), 0)
836 output = self.output.getvalue()
837 self.assertEqual("Cog version %s\n" % __version__, output)
839 def produces_help(self, args):
840 self.new_cog()
841 argv = ["argv0"] + args.split()
842 self.assertEqual(self.cog.main(argv), 0)
843 output = self.output.getvalue()
844 self.assertRegex(output, f"^{re.escape(description)}.*")
846 def test_dash_h(self):
847 # -h, --help, or -? anywhere on the command line should just print help.
848 self.produces_help("-h")
849 self.produces_help("--help")
850 self.produces_help("-?")
851 self.produces_help("fooey.txt -h")
852 self.produces_help("fooey.txt --help")
853 self.produces_help("-o -r @fooey.txt -? @booey.txt")
855 def test_dash_o_and_dash_r(self):
856 d = {
857 "cogfile.txt": """\
858 # Please run cog
859 """
860 }
862 make_files(d)
863 with self.assertRaisesRegex(
864 CogUsageError, r"^Can't use -o with -r \(they are opposites\)$"
865 ):
866 self.cog.callable_main(["argv0", "-o", "foo", "-r", "cogfile.txt"])
868 def test_dash_z(self):
869 d = {
870 "test.cog": """\
871 // This is my C++ file.
872 //[[[cog
873 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
874 for fn in fnames:
875 cog.outl("void %s();" % fn)
876 //]]]
877 """,
878 "test.out": """\
879 // This is my C++ file.
880 //[[[cog
881 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
882 for fn in fnames:
883 cog.outl("void %s();" % fn)
884 //]]]
885 void DoSomething();
886 void DoAnotherThing();
887 void DoLastThing();
888 """,
889 }
891 make_files(d)
892 with self.assertRaisesRegex(
893 CogError, r"^test.cog\(6\): Missing '\[\[\[end\]\]\]' before end of file.$"
894 ):
895 self.cog.callable_main(["argv0", "-r", "test.cog"])
896 self.new_cog()
897 self.cog.callable_main(["argv0", "-r", "-z", "test.cog"])
898 self.assertFilesSame("test.cog", "test.out")
900 def test_bad_dash_d(self):
901 with self.assertRaisesRegex(
902 CogUsageError, r"^argument -D: takes a name=value argument$"
903 ):
904 self.cog.callable_main(["argv0", "-Dfooey", "cog.txt"])
905 with self.assertRaisesRegex(
906 CogUsageError, r"^argument -D: takes a name=value argument$"
907 ):
908 self.cog.callable_main(["argv0", "-D", "fooey", "cog.txt"])
910 def test_bad_markers(self):
911 with self.assertRaisesRegex(
912 CogUsageError,
913 r"^argument --markers: requires 3 values separated by spaces, could not parse 'X'$",
914 ):
915 self.cog.callable_main(["argv0", "--markers=X"])
916 with self.assertRaisesRegex(
917 CogUsageError,
918 r"^argument --markers: requires 3 values separated by spaces, could not parse 'A B C D'$",
919 ):
920 self.cog.callable_main(["argv0", "--markers=A B C D"])
923class TestMain(TestCaseWithTempDir):
924 def setUp(self):
925 super().setUp()
926 self.old_argv = sys.argv[:]
927 self.old_stderr = sys.stderr
928 sys.stderr = io.StringIO()
930 def tearDown(self):
931 sys.stderr = self.old_stderr
932 sys.argv = self.old_argv
933 sys.modules.pop("mycode", None)
934 super().tearDown()
936 def test_main_function(self):
937 sys.argv = ["argv0", "-Z"]
938 ret = main()
939 self.assertEqual(ret, 2)
940 stderr = sys.stderr.getvalue()
941 self.assertEqual(stderr, "unrecognized arguments: -Z\n(for help use --help)\n")
943 files = {
944 "test.cog": """\
945 //[[[cog
946 def func():
947 import mycode
948 mycode.boom()
949 //]]]
950 //[[[end]]]
951 -----
952 //[[[cog
953 func()
954 //]]]
955 //[[[end]]]
956 """,
957 "mycode.py": """\
958 def boom():
959 [][0]
960 """,
961 }
963 def test_error_report(self):
964 self.check_error_report()
966 def test_error_report_with_prologue(self):
967 self.check_error_report("-p", "#1\n#2")
969 def check_error_report(self, *args):
970 """Check that the error report is right."""
971 make_files(self.files)
972 sys.argv = ["argv0"] + list(args) + ["-r", "test.cog"]
973 main()
974 expected = reindent_block("""\
975 Traceback (most recent call last):
976 File "test.cog", line 9, in <module>
977 func()
978 File "test.cog", line 4, in func
979 mycode.boom()
980 File "MYCODE", line 2, in boom
981 [][0]
982 IndexError: list index out of range
983 """)
984 expected = expected.replace("MYCODE", os.path.abspath("mycode.py"))
985 assert expected == sys.stderr.getvalue()
987 def test_error_in_prologue(self):
988 make_files(self.files)
989 sys.argv = ["argv0", "-p", "import mycode; mycode.boom()", "-r", "test.cog"]
990 main()
991 expected = reindent_block("""\
992 Traceback (most recent call last):
993 File "<prologue>", line 1, in <module>
994 import mycode; mycode.boom()
995 File "MYCODE", line 2, in boom
996 [][0]
997 IndexError: list index out of range
998 """)
999 expected = expected.replace("MYCODE", os.path.abspath("mycode.py"))
1000 assert expected == sys.stderr.getvalue()
1003class TestFileHandling(TestCaseWithTempDir):
1004 def test_simple(self):
1005 d = {
1006 "test.cog": """\
1007 // This is my C++ file.
1008 //[[[cog
1009 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1010 for fn in fnames:
1011 cog.outl("void %s();" % fn)
1012 //]]]
1013 //[[[end]]]
1014 """,
1015 "test.out": """\
1016 // This is my C++ file.
1017 //[[[cog
1018 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1019 for fn in fnames:
1020 cog.outl("void %s();" % fn)
1021 //]]]
1022 void DoSomething();
1023 void DoAnotherThing();
1024 void DoLastThing();
1025 //[[[end]]]
1026 """,
1027 }
1029 make_files(d)
1030 self.cog.callable_main(["argv0", "-r", "test.cog"])
1031 self.assertFilesSame("test.cog", "test.out")
1032 output = self.output.getvalue()
1033 self.assertIn("(changed)", output)
1035 def test_print_output(self):
1036 d = {
1037 "test.cog": """\
1038 // This is my C++ file.
1039 //[[[cog
1040 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1041 for fn in fnames:
1042 print("void %s();" % fn)
1043 //]]]
1044 //[[[end]]]
1045 """,
1046 "test.out": """\
1047 // This is my C++ file.
1048 //[[[cog
1049 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1050 for fn in fnames:
1051 print("void %s();" % fn)
1052 //]]]
1053 void DoSomething();
1054 void DoAnotherThing();
1055 void DoLastThing();
1056 //[[[end]]]
1057 """,
1058 }
1060 make_files(d)
1061 self.cog.callable_main(["argv0", "-rP", "test.cog"])
1062 self.assertFilesSame("test.cog", "test.out")
1063 output = self.output.getvalue()
1064 self.assertIn("(changed)", output)
1066 def test_wildcards(self):
1067 d = {
1068 "test.cog": """\
1069 // This is my C++ file.
1070 //[[[cog
1071 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1072 for fn in fnames:
1073 cog.outl("void %s();" % fn)
1074 //]]]
1075 //[[[end]]]
1076 """,
1077 "test2.cog": """\
1078 // This is my C++ file.
1079 //[[[cog
1080 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1081 for fn in fnames:
1082 cog.outl("void %s();" % fn)
1083 //]]]
1084 //[[[end]]]
1085 """,
1086 "test.out": """\
1087 // This is my C++ file.
1088 //[[[cog
1089 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1090 for fn in fnames:
1091 cog.outl("void %s();" % fn)
1092 //]]]
1093 void DoSomething();
1094 void DoAnotherThing();
1095 void DoLastThing();
1096 //[[[end]]]
1097 """,
1098 "not_this_one.cog": """\
1099 // This is my C++ file.
1100 //[[[cog
1101 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1102 for fn in fnames:
1103 cog.outl("void %s();" % fn)
1104 //]]]
1105 //[[[end]]]
1106 """,
1107 "not_this_one.out": """\
1108 // This is my C++ file.
1109 //[[[cog
1110 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1111 for fn in fnames:
1112 cog.outl("void %s();" % fn)
1113 //]]]
1114 //[[[end]]]
1115 """,
1116 }
1118 make_files(d)
1119 self.cog.callable_main(["argv0", "-r", "t*.cog"])
1120 self.assertFilesSame("test.cog", "test.out")
1121 self.assertFilesSame("test2.cog", "test.out")
1122 self.assertFilesSame("not_this_one.cog", "not_this_one.out")
1123 output = self.output.getvalue()
1124 self.assertIn("(changed)", output)
1126 def test_output_file(self):
1127 # -o sets the output file.
1128 d = {
1129 "test.cog": """\
1130 // This is my C++ file.
1131 //[[[cog
1132 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1133 for fn in fnames:
1134 cog.outl("void %s();" % fn)
1135 //]]]
1136 //[[[end]]]
1137 """,
1138 "test.out": """\
1139 // This is my C++ file.
1140 //[[[cog
1141 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1142 for fn in fnames:
1143 cog.outl("void %s();" % fn)
1144 //]]]
1145 void DoSomething();
1146 void DoAnotherThing();
1147 void DoLastThing();
1148 //[[[end]]]
1149 """,
1150 }
1152 make_files(d)
1153 self.cog.callable_main(["argv0", "-o", "in/a/dir/test.cogged", "test.cog"])
1154 self.assertFilesSame("in/a/dir/test.cogged", "test.out")
1156 def test_at_file(self):
1157 d = {
1158 "one.cog": """\
1159 //[[[cog
1160 cog.outl("hello world")
1161 //]]]
1162 //[[[end]]]
1163 """,
1164 "one.out": """\
1165 //[[[cog
1166 cog.outl("hello world")
1167 //]]]
1168 hello world
1169 //[[[end]]]
1170 """,
1171 "two.cog": """\
1172 //[[[cog
1173 cog.outl("goodbye cruel world")
1174 //]]]
1175 //[[[end]]]
1176 """,
1177 "two.out": """\
1178 //[[[cog
1179 cog.outl("goodbye cruel world")
1180 //]]]
1181 goodbye cruel world
1182 //[[[end]]]
1183 """,
1184 "cogfiles.txt": """\
1185 # Please run cog
1186 one.cog
1188 two.cog
1189 """,
1190 }
1192 make_files(d)
1193 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1194 self.assertFilesSame("one.cog", "one.out")
1195 self.assertFilesSame("two.cog", "two.out")
1196 output = self.output.getvalue()
1197 self.assertIn("(changed)", output)
1199 def test_nested_at_file(self):
1200 d = {
1201 "one.cog": """\
1202 //[[[cog
1203 cog.outl("hello world")
1204 //]]]
1205 //[[[end]]]
1206 """,
1207 "one.out": """\
1208 //[[[cog
1209 cog.outl("hello world")
1210 //]]]
1211 hello world
1212 //[[[end]]]
1213 """,
1214 "two.cog": """\
1215 //[[[cog
1216 cog.outl("goodbye cruel world")
1217 //]]]
1218 //[[[end]]]
1219 """,
1220 "two.out": """\
1221 //[[[cog
1222 cog.outl("goodbye cruel world")
1223 //]]]
1224 goodbye cruel world
1225 //[[[end]]]
1226 """,
1227 "cogfiles.txt": """\
1228 # Please run cog
1229 one.cog
1230 @cogfiles2.txt
1231 """,
1232 "cogfiles2.txt": """\
1233 # This one too, please.
1234 two.cog
1235 """,
1236 }
1238 make_files(d)
1239 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1240 self.assertFilesSame("one.cog", "one.out")
1241 self.assertFilesSame("two.cog", "two.out")
1242 output = self.output.getvalue()
1243 self.assertIn("(changed)", output)
1245 def test_at_file_with_args(self):
1246 d = {
1247 "both.cog": """\
1248 //[[[cog
1249 cog.outl("one: %s" % ('one' in globals()))
1250 cog.outl("two: %s" % ('two' in globals()))
1251 //]]]
1252 //[[[end]]]
1253 """,
1254 "one.out": """\
1255 //[[[cog
1256 cog.outl("one: %s" % ('one' in globals()))
1257 cog.outl("two: %s" % ('two' in globals()))
1258 //]]]
1259 one: True // ONE
1260 two: False // ONE
1261 //[[[end]]]
1262 """,
1263 "two.out": """\
1264 //[[[cog
1265 cog.outl("one: %s" % ('one' in globals()))
1266 cog.outl("two: %s" % ('two' in globals()))
1267 //]]]
1268 one: False // TWO
1269 two: True // TWO
1270 //[[[end]]]
1271 """,
1272 "cogfiles.txt": """\
1273 # Please run cog
1274 both.cog -o in/a/dir/both.one -s ' // ONE' -D one=x
1275 both.cog -o in/a/dir/both.two -s ' // TWO' -D two=x
1276 """,
1277 }
1279 make_files(d)
1280 self.cog.callable_main(["argv0", "@cogfiles.txt"])
1281 self.assertFilesSame("in/a/dir/both.one", "one.out")
1282 self.assertFilesSame("in/a/dir/both.two", "two.out")
1284 def test_at_file_with_bad_arg_combo(self):
1285 d = {
1286 "both.cog": """\
1287 //[[[cog
1288 cog.outl("one: %s" % ('one' in globals()))
1289 cog.outl("two: %s" % ('two' in globals()))
1290 //]]]
1291 //[[[end]]]
1292 """,
1293 "cogfiles.txt": """\
1294 # Please run cog
1295 both.cog
1296 both.cog -d # This is bad: -r and -d
1297 """,
1298 }
1300 make_files(d)
1301 with self.assertRaisesRegex(
1302 CogUsageError,
1303 r"^Can't use -d with -r \(or you would delete all your source!\)$",
1304 ):
1305 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1307 def test_at_file_with_tricky_filenames(self):
1308 def fix_backslashes(files_txt):
1309 """Make the contents of a files.txt sensitive to the platform."""
1310 if sys.platform != "win32": 1310 ↛ 1312line 1310 didn't jump to line 1312 because the condition on line 1310 was always true
1311 files_txt = files_txt.replace("\\", "/")
1312 return files_txt
1314 d = {
1315 "one 1.cog": """\
1316 //[[[cog cog.outl("hello world") ]]]
1317 """,
1318 "one.out": """\
1319 //[[[cog cog.outl("hello world") ]]]
1320 hello world //xxx
1321 """,
1322 "subdir": {
1323 "subback.cog": """\
1324 //[[[cog cog.outl("down deep with backslashes") ]]]
1325 """,
1326 "subfwd.cog": """\
1327 //[[[cog cog.outl("down deep with slashes") ]]]
1328 """,
1329 },
1330 "subback.out": """\
1331 //[[[cog cog.outl("down deep with backslashes") ]]]
1332 down deep with backslashes //yyy
1333 """,
1334 "subfwd.out": """\
1335 //[[[cog cog.outl("down deep with slashes") ]]]
1336 down deep with slashes //zzz
1337 """,
1338 "cogfiles.txt": fix_backslashes("""\
1339 # Please run cog
1340 'one 1.cog' -s ' //xxx'
1341 subdir\\subback.cog -s ' //yyy'
1342 subdir/subfwd.cog -s ' //zzz'
1343 """),
1344 }
1346 make_files(d)
1347 self.cog.callable_main(["argv0", "-z", "-r", "@cogfiles.txt"])
1348 self.assertFilesSame("one 1.cog", "one.out")
1349 self.assertFilesSame("subdir/subback.cog", "subback.out")
1350 self.assertFilesSame("subdir/subfwd.cog", "subfwd.out")
1352 def test_amp_file(self):
1353 d = {
1354 "code": {
1355 "files_to_cog": """\
1356 # A locally resolved file name.
1357 test.cog
1358 """,
1359 "test.cog": """\
1360 //[[[cog
1361 import myampsubmodule
1362 //]]]
1363 //[[[end]]]
1364 """,
1365 "test.out": """\
1366 //[[[cog
1367 import myampsubmodule
1368 //]]]
1369 Hello from myampsubmodule
1370 //[[[end]]]
1371 """,
1372 "myampsubmodule.py": """\
1373 import cog
1374 cog.outl("Hello from myampsubmodule")
1375 """,
1376 }
1377 }
1379 make_files(d)
1380 print(os.path.abspath("code/test.out"))
1381 self.cog.callable_main(["argv0", "-r", "&code/files_to_cog"])
1382 self.assertFilesSame("code/test.cog", "code/test.out")
1384 def run_with_verbosity(self, verbosity):
1385 d = {
1386 "unchanged.cog": """\
1387 //[[[cog
1388 cog.outl("hello world")
1389 //]]]
1390 hello world
1391 //[[[end]]]
1392 """,
1393 "changed.cog": """\
1394 //[[[cog
1395 cog.outl("goodbye cruel world")
1396 //]]]
1397 //[[[end]]]
1398 """,
1399 "cogfiles.txt": """\
1400 unchanged.cog
1401 changed.cog
1402 """,
1403 }
1405 make_files(d)
1406 self.cog.callable_main(
1407 ["argv0", "-r", "--verbosity=" + verbosity, "@cogfiles.txt"]
1408 )
1409 output = self.output.getvalue()
1410 return output
1412 def test_verbosity0(self):
1413 output = self.run_with_verbosity("0")
1414 self.assertEqual(output, "")
1416 def test_verbosity1(self):
1417 output = self.run_with_verbosity("1")
1418 self.assertEqual(output, "Cogging changed.cog (changed)\n")
1420 def test_verbosity2(self):
1421 output = self.run_with_verbosity("2")
1422 self.assertEqual(
1423 output, "Cogging unchanged.cog\nCogging changed.cog (changed)\n"
1424 )
1426 def test_change_dir(self):
1427 # The code can change directories, cog will move us back.
1428 d = {
1429 "sub": {
1430 "data.txt": "Hello!",
1431 },
1432 "test.cog": """\
1433 //[[[cog
1434 import os
1435 os.chdir("sub")
1436 cog.outl(open("data.txt").read())
1437 //]]]
1438 //[[[end]]]
1439 """,
1440 "test.out": """\
1441 //[[[cog
1442 import os
1443 os.chdir("sub")
1444 cog.outl(open("data.txt").read())
1445 //]]]
1446 Hello!
1447 //[[[end]]]
1448 """,
1449 }
1451 make_files(d)
1452 self.cog.callable_main(["argv0", "-r", "test.cog"])
1453 self.assertFilesSame("test.cog", "test.out")
1454 output = self.output.getvalue()
1455 self.assertIn("(changed)", output)
1458class CogTestLineEndings(TestCaseWithTempDir):
1459 """Tests for -U option (force LF line-endings in output)."""
1461 lines_in = [
1462 "Some text.",
1463 "//[[[cog",
1464 'cog.outl("Cog text")',
1465 "//]]]",
1466 "gobbledegook.",
1467 "//[[[end]]]",
1468 "epilogue.",
1469 "",
1470 ]
1472 lines_out = [
1473 "Some text.",
1474 "//[[[cog",
1475 'cog.outl("Cog text")',
1476 "//]]]",
1477 "Cog text",
1478 "//[[[end]]]",
1479 "epilogue.",
1480 "",
1481 ]
1483 def test_output_native_eol(self):
1484 make_files({"infile": "\n".join(self.lines_in)})
1485 self.cog.callable_main(["argv0", "-o", "outfile", "infile"])
1486 self.assertFileContent("outfile", os.linesep.join(self.lines_out))
1488 def test_output_lf_eol(self):
1489 make_files({"infile": "\n".join(self.lines_in)})
1490 self.cog.callable_main(["argv0", "-U", "-o", "outfile", "infile"])
1491 self.assertFileContent("outfile", "\n".join(self.lines_out))
1493 def test_replace_native_eol(self):
1494 make_files({"test.cog": "\n".join(self.lines_in)})
1495 self.cog.callable_main(["argv0", "-r", "test.cog"])
1496 self.assertFileContent("test.cog", os.linesep.join(self.lines_out))
1498 def test_replace_lf_eol(self):
1499 make_files({"test.cog": "\n".join(self.lines_in)})
1500 self.cog.callable_main(["argv0", "-U", "-r", "test.cog"])
1501 self.assertFileContent("test.cog", "\n".join(self.lines_out))
1504class CogTestCharacterEncoding(TestCaseWithTempDir):
1505 def test_simple(self):
1506 d = {
1507 "test.cog": b"""\
1508 // This is my C++ file.
1509 //[[[cog
1510 cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)")
1511 //]]]
1512 //[[[end]]]
1513 """,
1514 "test.out": b"""\
1515 // This is my C++ file.
1516 //[[[cog
1517 cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)")
1518 //]]]
1519 // Unicode: \xe1\x88\xb4 (U+1234)
1520 //[[[end]]]
1521 """.replace(b"\n", os.linesep.encode()),
1522 }
1524 make_files(d)
1525 self.cog.callable_main(["argv0", "-r", "test.cog"])
1526 self.assertFilesSame("test.cog", "test.out")
1527 output = self.output.getvalue()
1528 self.assertIn("(changed)", output)
1530 def test_file_encoding_option(self):
1531 d = {
1532 "test.cog": b"""\
1533 // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows
1534 //[[[cog
1535 cog.outl("\xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe")
1536 //]]]
1537 //[[[end]]]
1538 """,
1539 "test.out": b"""\
1540 // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows
1541 //[[[cog
1542 cog.outl("\xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe")
1543 //]]]
1544 \xd1\xfa\xe5\xf8\xfc \xe5\xf9\xb8 \xfd\xf2\xe8\xf5 \xec\xff\xe3\xea\xe8\xf5 \xf4\xf0\xe0\xed\xf6\xf3\xe7\xf1\xea\xe8\xf5 \xe1\xf3\xeb\xee\xea \xe4\xe0 \xe2\xfb\xef\xe5\xe9 \xf7\xe0\xfe
1545 //[[[end]]]
1546 """.replace(b"\n", os.linesep.encode()),
1547 }
1549 make_files(d)
1550 self.cog.callable_main(["argv0", "-n", "cp1251", "-r", "test.cog"])
1551 self.assertFilesSame("test.cog", "test.out")
1552 output = self.output.getvalue()
1553 self.assertIn("(changed)", output)
1556class TestCaseWithImports(TestCaseWithTempDir):
1557 """Automatic resetting of sys.modules for tests that import modules.
1559 When running tests which import modules, the sys.modules list
1560 leaks from one test to the next. This test case class scrubs
1561 the list after each run to keep the tests isolated from each other.
1563 """
1565 def setUp(self):
1566 super().setUp()
1567 self.sysmodulekeys = list(sys.modules)
1569 def tearDown(self):
1570 modstoscrub = [
1571 modname for modname in sys.modules if modname not in self.sysmodulekeys
1572 ]
1573 for modname in modstoscrub:
1574 del sys.modules[modname]
1575 super().tearDown()
1578class CogIncludeTests(TestCaseWithImports):
1579 dincludes = {
1580 "test.cog": """\
1581 //[[[cog
1582 import mymodule
1583 //]]]
1584 //[[[end]]]
1585 """,
1586 "test.out": """\
1587 //[[[cog
1588 import mymodule
1589 //]]]
1590 Hello from mymodule
1591 //[[[end]]]
1592 """,
1593 "test2.out": """\
1594 //[[[cog
1595 import mymodule
1596 //]]]
1597 Hello from mymodule in inc2
1598 //[[[end]]]
1599 """,
1600 "include": {
1601 "mymodule.py": """\
1602 import cog
1603 cog.outl("Hello from mymodule")
1604 """
1605 },
1606 "inc2": {
1607 "mymodule.py": """\
1608 import cog
1609 cog.outl("Hello from mymodule in inc2")
1610 """
1611 },
1612 "inc3": {
1613 "someothermodule.py": """\
1614 import cog
1615 cog.outl("This is some other module.")
1616 """
1617 },
1618 }
1620 def test_need_include_path(self):
1621 # Try it without the -I, to see that an ImportError happens.
1622 make_files(self.dincludes)
1623 msg = "(ImportError|ModuleNotFoundError): No module named '?mymodule'?"
1624 with self.assertRaisesRegex(CogUserException, msg):
1625 self.cog.callable_main(["argv0", "-r", "test.cog"])
1627 def test_include_path(self):
1628 # Test that -I adds include directories properly.
1629 make_files(self.dincludes)
1630 self.cog.callable_main(["argv0", "-r", "-I", "include", "test.cog"])
1631 self.assertFilesSame("test.cog", "test.out")
1633 def test_two_include_paths(self):
1634 # Test that two -I's add include directories properly.
1635 make_files(self.dincludes)
1636 self.cog.callable_main(
1637 ["argv0", "-r", "-I", "include", "-I", "inc2", "test.cog"]
1638 )
1639 self.assertFilesSame("test.cog", "test.out")
1641 def test_two_include_paths2(self):
1642 # Test that two -I's add include directories properly.
1643 make_files(self.dincludes)
1644 self.cog.callable_main(
1645 ["argv0", "-r", "-I", "inc2", "-I", "include", "test.cog"]
1646 )
1647 self.assertFilesSame("test.cog", "test2.out")
1649 def test_useless_include_path(self):
1650 # Test that the search will continue past the first directory.
1651 make_files(self.dincludes)
1652 self.cog.callable_main(
1653 ["argv0", "-r", "-I", "inc3", "-I", "include", "test.cog"]
1654 )
1655 self.assertFilesSame("test.cog", "test.out")
1657 def test_sys_path_is_unchanged(self):
1658 d = {
1659 "bad.cog": """\
1660 //[[[cog cog.error("Oh no!") ]]]
1661 //[[[end]]]
1662 """,
1663 "good.cog": """\
1664 //[[[cog cog.outl("Oh yes!") ]]]
1665 //[[[end]]]
1666 """,
1667 }
1669 make_files(d)
1670 # Is it unchanged just by creating a cog engine?
1671 oldsyspath = sys.path[:]
1672 self.new_cog()
1673 self.assertEqual(oldsyspath, sys.path)
1674 # Is it unchanged for a successful run?
1675 self.new_cog()
1676 self.cog.callable_main(["argv0", "-r", "good.cog"])
1677 self.assertEqual(oldsyspath, sys.path)
1678 # Is it unchanged for a successful run with includes?
1679 self.new_cog()
1680 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "good.cog"])
1681 self.assertEqual(oldsyspath, sys.path)
1682 # Is it unchanged for a successful run with two includes?
1683 self.new_cog()
1684 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "-I", "quux", "good.cog"])
1685 self.assertEqual(oldsyspath, sys.path)
1686 # Is it unchanged for a failed run?
1687 self.new_cog()
1688 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1689 self.cog.callable_main(["argv0", "-r", "bad.cog"])
1690 self.assertEqual(oldsyspath, sys.path)
1691 # Is it unchanged for a failed run with includes?
1692 self.new_cog()
1693 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1694 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "bad.cog"])
1695 self.assertEqual(oldsyspath, sys.path)
1696 # Is it unchanged for a failed run with two includes?
1697 self.new_cog()
1698 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1699 self.cog.callable_main(
1700 ["argv0", "-r", "-I", "xyzzy", "-I", "quux", "bad.cog"]
1701 )
1702 self.assertEqual(oldsyspath, sys.path)
1704 def test_sub_directories(self):
1705 # Test that relative paths on the command line work, with includes.
1707 d = {
1708 "code": {
1709 "test.cog": """\
1710 //[[[cog
1711 import mysubmodule
1712 //]]]
1713 //[[[end]]]
1714 """,
1715 "test.out": """\
1716 //[[[cog
1717 import mysubmodule
1718 //]]]
1719 Hello from mysubmodule
1720 //[[[end]]]
1721 """,
1722 "mysubmodule.py": """\
1723 import cog
1724 cog.outl("Hello from mysubmodule")
1725 """,
1726 }
1727 }
1729 make_files(d)
1730 # We should be able to invoke cog without the -I switch, and it will
1731 # auto-include the current directory
1732 self.cog.callable_main(["argv0", "-r", "code/test.cog"])
1733 self.assertFilesSame("code/test.cog", "code/test.out")
1736class CogTestsInFiles(TestCaseWithTempDir):
1737 def test_warn_if_no_cog_code(self):
1738 # Test that the -e switch warns if there is no Cog code.
1739 d = {
1740 "with.cog": """\
1741 //[[[cog
1742 cog.outl("hello world")
1743 //]]]
1744 hello world
1745 //[[[end]]]
1746 """,
1747 "without.cog": """\
1748 There's no cog
1749 code in this file.
1750 """,
1751 }
1753 make_files(d)
1754 self.cog.callable_main(["argv0", "-e", "with.cog"])
1755 output = self.output.getvalue()
1756 self.assertNotIn("Warning", output)
1757 self.new_cog()
1758 self.cog.callable_main(["argv0", "-e", "without.cog"])
1759 output = self.output.getvalue()
1760 self.assertIn("Warning: no cog code found in without.cog", output)
1761 self.new_cog()
1762 self.cog.callable_main(["argv0", "without.cog"])
1763 output = self.output.getvalue()
1764 self.assertNotIn("Warning", output)
1766 def test_file_name_props(self):
1767 d = {
1768 "cog1.txt": """\
1769 //[[[cog
1770 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1771 //]]]
1772 this is cog1.txt in, cog1.txt out
1773 [[[end]]]
1774 """,
1775 "cog1.out": """\
1776 //[[[cog
1777 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1778 //]]]
1779 This is cog1.txt in, cog1.txt out
1780 [[[end]]]
1781 """,
1782 "cog1out.out": """\
1783 //[[[cog
1784 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1785 //]]]
1786 This is cog1.txt in, cog1out.txt out
1787 [[[end]]]
1788 """,
1789 }
1791 make_files(d)
1792 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
1793 self.assertFilesSame("cog1.txt", "cog1.out")
1794 self.new_cog()
1795 self.cog.callable_main(["argv0", "-o", "cog1out.txt", "cog1.txt"])
1796 self.assertFilesSame("cog1out.txt", "cog1out.out")
1798 def test_globals_dont_cross_files(self):
1799 # Make sure that global values don't get shared between files.
1800 d = {
1801 "one.cog": """\
1802 //[[[cog s = "This was set in one.cog" ]]]
1803 //[[[end]]]
1804 //[[[cog cog.outl(s) ]]]
1805 //[[[end]]]
1806 """,
1807 "one.out": """\
1808 //[[[cog s = "This was set in one.cog" ]]]
1809 //[[[end]]]
1810 //[[[cog cog.outl(s) ]]]
1811 This was set in one.cog
1812 //[[[end]]]
1813 """,
1814 "two.cog": """\
1815 //[[[cog
1816 try:
1817 cog.outl(s)
1818 except NameError:
1819 cog.outl("s isn't set!")
1820 //]]]
1821 //[[[end]]]
1822 """,
1823 "two.out": """\
1824 //[[[cog
1825 try:
1826 cog.outl(s)
1827 except NameError:
1828 cog.outl("s isn't set!")
1829 //]]]
1830 s isn't set!
1831 //[[[end]]]
1832 """,
1833 "cogfiles.txt": """\
1834 # Please run cog
1835 one.cog
1837 two.cog
1838 """,
1839 }
1841 make_files(d)
1842 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1843 self.assertFilesSame("one.cog", "one.out")
1844 self.assertFilesSame("two.cog", "two.out")
1845 output = self.output.getvalue()
1846 self.assertIn("(changed)", output)
1848 def test_remove_generated_output(self):
1849 d = {
1850 "cog1.txt": """\
1851 //[[[cog
1852 cog.outl("This line was generated.")
1853 //]]]
1854 This line was generated.
1855 //[[[end]]]
1856 This line was not.
1857 """,
1858 "cog1.out": """\
1859 //[[[cog
1860 cog.outl("This line was generated.")
1861 //]]]
1862 //[[[end]]]
1863 This line was not.
1864 """,
1865 "cog1.out2": """\
1866 //[[[cog
1867 cog.outl("This line was generated.")
1868 //]]]
1869 This line was generated.
1870 //[[[end]]]
1871 This line was not.
1872 """,
1873 }
1875 make_files(d)
1876 # Remove generated output.
1877 self.cog.callable_main(["argv0", "-r", "-x", "cog1.txt"])
1878 self.assertFilesSame("cog1.txt", "cog1.out")
1879 self.new_cog()
1880 # Regenerate the generated output.
1881 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
1882 self.assertFilesSame("cog1.txt", "cog1.out2")
1883 self.new_cog()
1884 # Remove the generated output again.
1885 self.cog.callable_main(["argv0", "-r", "-x", "cog1.txt"])
1886 self.assertFilesSame("cog1.txt", "cog1.out")
1888 def test_msg_call(self):
1889 infile = """\
1890 #[[[cog
1891 cog.msg("Hello there!")
1892 #]]]
1893 #[[[end]]]
1894 """
1895 infile = reindent_block(infile)
1896 self.assertEqual(self.cog.process_string(infile), infile)
1897 output = self.output.getvalue()
1898 self.assertEqual(output, "Message: Hello there!\n")
1900 def test_error_message_has_no_traceback(self):
1901 # Test that a Cog error is printed to stderr with no traceback.
1903 d = {
1904 "cog1.txt": """\
1905 //[[[cog
1906 cog.outl("This line was newly")
1907 cog.outl("generated by cog")
1908 cog.outl("blah blah.")
1909 //]]]
1910 Xhis line was newly
1911 generated by cog
1912 blah blah.
1913 //[[[end]]] (sum: qFQJguWta5)
1914 """,
1915 }
1917 make_files(d)
1918 stderr = io.StringIO()
1919 self.cog.set_output(stderr=stderr)
1920 self.cog.main(["argv0", "-c", "-r", "cog1.txt"])
1921 self.assertEqual(self.output.getvalue(), "Cogging cog1.txt\n")
1922 self.assertEqual(
1923 stderr.getvalue(),
1924 "cog1.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
1925 )
1927 def test_dash_d(self):
1928 d = {
1929 "test.cog": """\
1930 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1931 --[[[end]]]
1932 """,
1933 "test.kablooey": """\
1934 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1935 Defined fooey as kablooey
1936 --[[[end]]]
1937 """,
1938 "test.einstein": """\
1939 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1940 Defined fooey as e=mc2
1941 --[[[end]]]
1942 """,
1943 }
1945 make_files(d)
1946 self.cog.callable_main(["argv0", "-r", "-D", "fooey=kablooey", "test.cog"])
1947 self.assertFilesSame("test.cog", "test.kablooey")
1948 make_files(d)
1949 self.cog.callable_main(["argv0", "-r", "-Dfooey=kablooey", "test.cog"])
1950 self.assertFilesSame("test.cog", "test.kablooey")
1951 make_files(d)
1952 self.cog.callable_main(["argv0", "-r", "-Dfooey=e=mc2", "test.cog"])
1953 self.assertFilesSame("test.cog", "test.einstein")
1954 make_files(d)
1955 self.cog.callable_main(
1956 ["argv0", "-r", "-Dbar=quux", "-Dfooey=kablooey", "test.cog"]
1957 )
1958 self.assertFilesSame("test.cog", "test.kablooey")
1959 make_files(d)
1960 self.cog.callable_main(
1961 ["argv0", "-r", "-Dfooey=kablooey", "-Dbar=quux", "test.cog"]
1962 )
1963 self.assertFilesSame("test.cog", "test.kablooey")
1964 make_files(d)
1965 self.cog.callable_main(
1966 ["argv0", "-r", "-Dfooey=gooey", "-Dfooey=kablooey", "test.cog"]
1967 )
1968 self.assertFilesSame("test.cog", "test.kablooey")
1970 def test_output_to_stdout(self):
1971 d = {
1972 "test.cog": """\
1973 --[[[cog cog.outl('Hey there!') ]]]
1974 --[[[end]]]
1975 """
1976 }
1978 make_files(d)
1979 stderr = io.StringIO()
1980 self.cog.set_output(stderr=stderr)
1981 self.cog.callable_main(["argv0", "test.cog"])
1982 output = self.output.getvalue()
1983 outerr = stderr.getvalue()
1984 self.assertEqual(
1985 output, "--[[[cog cog.outl('Hey there!') ]]]\nHey there!\n--[[[end]]]\n"
1986 )
1987 self.assertEqual(outerr, "")
1989 def test_read_from_stdin(self):
1990 stdin = io.StringIO("--[[[cog cog.outl('Wow') ]]]\n--[[[end]]]\n")
1992 def restore_stdin(old_stdin):
1993 sys.stdin = old_stdin
1995 self.addCleanup(restore_stdin, sys.stdin)
1996 sys.stdin = stdin
1998 stderr = io.StringIO()
1999 self.cog.set_output(stderr=stderr)
2000 self.cog.callable_main(["argv0", "-"])
2001 output = self.output.getvalue()
2002 outerr = stderr.getvalue()
2003 self.assertEqual(output, "--[[[cog cog.outl('Wow') ]]]\nWow\n--[[[end]]]\n")
2004 self.assertEqual(outerr, "")
2006 def test_suffix_output_lines(self):
2007 d = {
2008 "test.cog": """\
2009 Hey there.
2010 ;[[[cog cog.outl('a\\nb\\n \\nc') ]]]
2011 ;[[[end]]]
2012 Good bye.
2013 """,
2014 "test.out": """\
2015 Hey there.
2016 ;[[[cog cog.outl('a\\nb\\n \\nc') ]]]
2017 a (foo)
2018 b (foo)
2019 """ # These three trailing spaces are important.
2020 # The suffix is not applied to completely blank lines.
2021 """
2022 c (foo)
2023 ;[[[end]]]
2024 Good bye.
2025 """,
2026 }
2028 make_files(d)
2029 self.cog.callable_main(["argv0", "-r", "-s", " (foo)", "test.cog"])
2030 self.assertFilesSame("test.cog", "test.out")
2032 def test_empty_suffix(self):
2033 d = {
2034 "test.cog": """\
2035 ;[[[cog cog.outl('a\\nb\\nc') ]]]
2036 ;[[[end]]]
2037 """,
2038 "test.out": """\
2039 ;[[[cog cog.outl('a\\nb\\nc') ]]]
2040 a
2041 b
2042 c
2043 ;[[[end]]]
2044 """,
2045 }
2047 make_files(d)
2048 self.cog.callable_main(["argv0", "-r", "-s", "", "test.cog"])
2049 self.assertFilesSame("test.cog", "test.out")
2051 def test_hellish_suffix(self):
2052 d = {
2053 "test.cog": """\
2054 ;[[[cog cog.outl('a\\n\\nb') ]]]
2055 """,
2056 "test.out": """\
2057 ;[[[cog cog.outl('a\\n\\nb') ]]]
2058 a /\\n*+([)]><
2060 b /\\n*+([)]><
2061 """,
2062 }
2064 make_files(d)
2065 self.cog.callable_main(["argv0", "-z", "-r", "-s", r" /\n*+([)]><", "test.cog"])
2066 self.assertFilesSame("test.cog", "test.out")
2068 def test_prologue(self):
2069 d = {
2070 "test.cog": """\
2071 Some text.
2072 //[[[cog cog.outl(str(math.sqrt(2))[:12])]]]
2073 //[[[end]]]
2074 epilogue.
2075 """,
2076 "test.out": """\
2077 Some text.
2078 //[[[cog cog.outl(str(math.sqrt(2))[:12])]]]
2079 1.4142135623
2080 //[[[end]]]
2081 epilogue.
2082 """,
2083 }
2085 make_files(d)
2086 self.cog.callable_main(["argv0", "-r", "-p", "import math", "test.cog"])
2087 self.assertFilesSame("test.cog", "test.out")
2089 def test_threads(self):
2090 # Test that the implicitly imported cog module is actually different for
2091 # different threads.
2092 numthreads = 20
2094 d = {}
2095 for i in range(numthreads):
2096 d[f"f{i}.cog"] = (
2097 "x\n" * i
2098 + "[[[cog\n"
2099 + f"assert cog.firstLineNum == int(FIRST) == {i + 1}\n"
2100 + "]]]\n"
2101 + "[[[end]]]\n"
2102 )
2103 make_files(d)
2105 results = []
2107 def thread_main(num):
2108 try:
2109 ret = Cog().main(
2110 ["cog.py", "-r", "-D", f"FIRST={num + 1}", f"f{num}.cog"]
2111 )
2112 assert ret == 0
2113 except Exception as exc: # pragma: no cover (only happens on test failure)
2114 results.append(exc)
2115 else:
2116 results.append(None)
2118 ts = [
2119 threading.Thread(target=thread_main, args=(i,)) for i in range(numthreads)
2120 ]
2121 for t in ts:
2122 t.start()
2123 for t in ts:
2124 t.join()
2125 assert results == [None] * numthreads
2128class CheckTests(TestCaseWithTempDir):
2129 def run_check(self, args, status=0):
2130 actual_status = self.cog.main(["argv0", "--check"] + args)
2131 print(self.output.getvalue())
2132 self.assertEqual(status, actual_status)
2134 def assert_made_files_unchanged(self, d):
2135 for name, content in d.items():
2136 content = reindent_block(content)
2137 if os.name == "nt": 2137 ↛ 2138line 2137 didn't jump to line 2138 because the condition on line 2137 was never true
2138 content = content.replace("\n", "\r\n")
2139 self.assertFileContent(name, content)
2141 def test_check_no_cog(self):
2142 d = {
2143 "hello.txt": """\
2144 Hello.
2145 """,
2146 }
2147 make_files(d)
2148 self.run_check(["hello.txt"], status=0)
2149 self.assertEqual(self.output.getvalue(), "Checking hello.txt\n")
2150 self.assert_made_files_unchanged(d)
2152 def test_check_good(self):
2153 d = {
2154 "unchanged.cog": """\
2155 //[[[cog
2156 cog.outl("hello world")
2157 //]]]
2158 hello world
2159 //[[[end]]]
2160 """,
2161 }
2162 make_files(d)
2163 self.run_check(["unchanged.cog"], status=0)
2164 self.assertEqual(self.output.getvalue(), "Checking unchanged.cog\n")
2165 self.assert_made_files_unchanged(d)
2167 def test_check_bad(self):
2168 d = {
2169 "changed.cog": """\
2170 //[[[cog
2171 cog.outl("goodbye world")
2172 //]]]
2173 hello world
2174 //[[[end]]]
2175 """,
2176 }
2177 make_files(d)
2178 self.run_check(["changed.cog"], status=5)
2179 self.assertEqual(
2180 self.output.getvalue(), "Checking changed.cog (changed)\nCheck failed\n"
2181 )
2182 self.assert_made_files_unchanged(d)
2184 def test_check_bad_with_diff(self):
2185 d = {
2186 "skittering.cog": """\
2187 //[[[cog
2188 for i in range(5): cog.outl(f"number {i}")
2189 cog.outl("goodbye world")
2190 //]]]
2191 number 0
2192 number 1
2193 number 2
2194 number 3
2195 number 4
2196 hello world
2197 //[[[end]]]
2198 """,
2199 }
2200 make_files(d)
2201 self.run_check(["--diff", "skittering.cog"], status=5)
2202 output = """\
2203 Checking skittering.cog (changed)
2204 --- current skittering.cog
2205 +++ changed skittering.cog
2206 @@ -7,5 +7,5 @@
2207 number 2
2208 number 3
2209 number 4
2210 -hello world
2211 +goodbye world
2212 //[[[end]]]
2213 Check failed
2214 """
2215 self.assertEqual(self.output.getvalue(), reindent_block(output))
2216 self.assert_made_files_unchanged(d)
2218 def test_check_bad_with_message(self):
2219 d = {
2220 "changed.cog": """\
2221 //[[[cog
2222 cog.outl("goodbye world")
2223 //]]]
2224 hello world
2225 //[[[end]]]
2226 """,
2227 }
2228 make_files(d)
2229 self.run_check(
2230 ["--check-fail-msg=Run `make cogged` to fix", "changed.cog"], status=5
2231 )
2232 self.assertEqual(
2233 self.output.getvalue(),
2234 "Checking changed.cog (changed)\nCheck failed: Run `make cogged` to fix\n",
2235 )
2236 self.assert_made_files_unchanged(d)
2238 def test_check_mixed(self):
2239 d = {
2240 "unchanged.cog": """\
2241 //[[[cog
2242 cog.outl("hello world")
2243 //]]]
2244 hello world
2245 //[[[end]]]
2246 """,
2247 "changed.cog": """\
2248 //[[[cog
2249 cog.outl("goodbye world")
2250 //]]]
2251 hello world
2252 //[[[end]]]
2253 """,
2254 }
2255 make_files(d)
2256 for verbosity, output in [
2257 ("0", "Check failed\n"),
2258 ("1", "Checking changed.cog (changed)\nCheck failed\n"),
2259 (
2260 "2",
2261 "Checking unchanged.cog\nChecking changed.cog (changed)\nCheck failed\n",
2262 ),
2263 ]:
2264 self.new_cog()
2265 self.run_check(
2266 ["--verbosity=%s" % verbosity, "unchanged.cog", "changed.cog"], status=5
2267 )
2268 self.assertEqual(self.output.getvalue(), output)
2269 self.assert_made_files_unchanged(d)
2271 def test_check_with_good_checksum(self):
2272 d = {
2273 "good.txt": """\
2274 //[[[cog
2275 cog.outl("This line was newly")
2276 cog.outl("generated by cog")
2277 cog.outl("blah blah.")
2278 //]]]
2279 This line was newly
2280 generated by cog
2281 blah blah.
2282 //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346)
2283 """,
2284 }
2285 make_files(d)
2286 # Have to use -c with --check if there are checksums in the file.
2287 self.run_check(["-c", "good.txt"], status=0)
2288 self.assertEqual(self.output.getvalue(), "Checking good.txt\n")
2289 self.assert_made_files_unchanged(d)
2291 def test_check_with_bad_checksum(self):
2292 d = {
2293 "bad.txt": """\
2294 //[[[cog
2295 cog.outl("This line was newly")
2296 cog.outl("generated by cog")
2297 cog.outl("blah blah.")
2298 //]]]
2299 This line was newly
2300 generated by cog
2301 blah blah.
2302 //[[[end]]] (checksum: a9999999e5ad6b95c9e9a184b26f4346)
2303 """,
2304 }
2305 make_files(d)
2306 # Have to use -c with --check if there are checksums in the file.
2307 self.run_check(["-c", "bad.txt"], status=1)
2308 self.assertEqual(
2309 self.output.getvalue(),
2310 "Checking bad.txt\nbad.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
2311 )
2312 self.assert_made_files_unchanged(d)
2314 def test_check_with_good_sum(self):
2315 d = {
2316 "good.txt": """\
2317 //[[[cog
2318 cog.outl("This line was newly")
2319 cog.outl("generated by cog")
2320 cog.outl("blah blah.")
2321 //]]]
2322 This line was newly
2323 generated by cog
2324 blah blah.
2325 //[[[end]]] (sum: qFQJguWta5)
2326 """,
2327 }
2328 make_files(d)
2329 # Have to use -c with --check if there are checksums in the file.
2330 self.run_check(["-c", "good.txt"], status=0)
2331 self.assertEqual(self.output.getvalue(), "Checking good.txt\n")
2332 self.assert_made_files_unchanged(d)
2334 def test_check_with_bad_sum(self):
2335 d = {
2336 "bad.txt": """\
2337 //[[[cog
2338 cog.outl("This line was newly")
2339 cog.outl("generated by cog")
2340 cog.outl("blah blah.")
2341 //]]]
2342 This line was newly
2343 generated by cog
2344 blah blah.
2345 //[[[end]]] (sum: qZmZmeWta5)
2346 """,
2347 }
2348 make_files(d)
2349 # Have to use -c with --check if there are checksums in the file.
2350 self.run_check(["-c", "bad.txt"], status=1)
2351 self.assertEqual(
2352 self.output.getvalue(),
2353 "Checking bad.txt\nbad.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
2354 )
2355 self.assert_made_files_unchanged(d)
2358class WritabilityTests(TestCaseWithTempDir):
2359 d = {
2360 "test.cog": """\
2361 //[[[cog
2362 for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']:
2363 cog.outl("void %s();" % fn)
2364 //]]]
2365 //[[[end]]]
2366 """,
2367 "test.out": """\
2368 //[[[cog
2369 for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']:
2370 cog.outl("void %s();" % fn)
2371 //]]]
2372 void DoSomething();
2373 void DoAnotherThing();
2374 void DoLastThing();
2375 //[[[end]]]
2376 """,
2377 }
2379 if os.name == "nt": 2379 ↛ 2381line 2379 didn't jump to line 2381 because the condition on line 2379 was never true
2380 # for Windows
2381 cmd_w_args = "attrib -R %s"
2382 cmd_w_asterisk = "attrib -R *"
2383 else:
2384 # for unix-like
2385 cmd_w_args = "chmod +w %s"
2386 cmd_w_asterisk = "chmod +w *"
2388 def setUp(self):
2389 super().setUp()
2390 make_files(self.d)
2391 self.testcog = os.path.join(self.tempdir, "test.cog")
2392 os.chmod(self.testcog, stat.S_IREAD) # Make the file readonly.
2393 assert not os.access(self.testcog, os.W_OK)
2395 def tearDown(self):
2396 os.chmod(self.testcog, stat.S_IWRITE) # Make the file writable again.
2397 super().tearDown()
2399 def test_readonly_no_command(self):
2400 with self.assertRaisesRegex(CogError, "^Can't overwrite test.cog$"):
2401 self.cog.callable_main(["argv0", "-r", "test.cog"])
2402 assert not os.access(self.testcog, os.W_OK)
2404 def test_readonly_with_command(self):
2405 self.cog.callable_main(["argv0", "-r", "-w", self.cmd_w_args, "test.cog"])
2406 self.assertFilesSame("test.cog", "test.out")
2407 assert os.access(self.testcog, os.W_OK)
2409 def test_readonly_with_command_with_no_slot(self):
2410 self.cog.callable_main(["argv0", "-r", "-w", self.cmd_w_asterisk, "test.cog"])
2411 self.assertFilesSame("test.cog", "test.out")
2412 assert os.access(self.testcog, os.W_OK)
2414 def test_readonly_with_ineffectual_command(self):
2415 with self.assertRaisesRegex(CogError, "^Couldn't make test.cog writable$"):
2416 self.cog.callable_main(["argv0", "-r", "-w", "echo %s", "test.cog"])
2417 assert not os.access(self.testcog, os.W_OK)
2420class ChecksumTests(TestCaseWithTempDir):
2421 def test_create_checksum_output(self):
2422 d = {
2423 "cog1.txt": """\
2424 //[[[cog
2425 cog.outl("This line was generated.")
2426 //]]]
2427 This line was generated.
2428 //[[[end]]] what
2429 This line was not.
2430 """,
2431 "cog1.out": """\
2432 //[[[cog
2433 cog.outl("This line was generated.")
2434 //]]]
2435 This line was generated.
2436 //[[[end]]] (sum: itsT+1m5lq) what
2437 This line was not.
2438 """,
2439 }
2441 make_files(d)
2442 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2443 self.assertFilesSame("cog1.txt", "cog1.out")
2445 def test_check_checksum_output(self):
2446 d = {
2447 "cog1.txt": """\
2448 //[[[cog
2449 cog.outl("This line was newly")
2450 cog.outl("generated by cog")
2451 cog.outl("blah blah.")
2452 //]]]
2453 This line was generated.
2454 //[[[end]]] (sum: itsT+1m5lq) end
2455 """,
2456 "cog1.out": """\
2457 //[[[cog
2458 cog.outl("This line was newly")
2459 cog.outl("generated by cog")
2460 cog.outl("blah blah.")
2461 //]]]
2462 This line was newly
2463 generated by cog
2464 blah blah.
2465 //[[[end]]] (sum: qFQJguWta5) end
2466 """,
2467 }
2469 make_files(d)
2470 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2471 self.assertFilesSame("cog1.txt", "cog1.out")
2473 def test_check_old_checksum_format(self):
2474 # Test that old checksum format can still be read
2475 d = {
2476 "cog1.txt": """\
2477 //[[[cog
2478 cog.outl("This line was newly")
2479 cog.outl("generated by cog")
2480 cog.outl("blah blah.")
2481 //]]]
2482 This line was generated.
2483 //[[[end]]] (checksum: 8adb13fb59b996a1c7f0065ea9f3d893) end
2484 """,
2485 "cog1.out": """\
2486 //[[[cog
2487 cog.outl("This line was newly")
2488 cog.outl("generated by cog")
2489 cog.outl("blah blah.")
2490 //]]]
2491 This line was newly
2492 generated by cog
2493 blah blah.
2494 //[[[end]]] (sum: qFQJguWta5) end
2495 """,
2496 }
2498 make_files(d)
2499 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2500 self.assertFilesSame("cog1.txt", "cog1.out")
2502 def test_remove_checksum_output(self):
2503 d = {
2504 "cog1.txt": """\
2505 //[[[cog
2506 cog.outl("This line was newly")
2507 cog.outl("generated by cog")
2508 cog.outl("blah blah.")
2509 //]]]
2510 This line was generated.
2511 //[[[end]]] (sum: itsT+1m5lq) fooey
2512 """,
2513 "cog1.out": """\
2514 //[[[cog
2515 cog.outl("This line was newly")
2516 cog.outl("generated by cog")
2517 cog.outl("blah blah.")
2518 //]]]
2519 This line was newly
2520 generated by cog
2521 blah blah.
2522 //[[[end]]] fooey
2523 """,
2524 }
2526 make_files(d)
2527 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
2528 self.assertFilesSame("cog1.txt", "cog1.out")
2530 def test_tampered_checksum_output(self):
2531 d = {
2532 "cog1.txt": """\
2533 //[[[cog
2534 cog.outl("This line was newly")
2535 cog.outl("generated by cog")
2536 cog.outl("blah blah.")
2537 //]]]
2538 Xhis line was newly
2539 generated by cog
2540 blah blah.
2541 //[[[end]]] (sum: qFQJguWta5)
2542 """,
2543 "cog2.txt": """\
2544 //[[[cog
2545 cog.outl("This line was newly")
2546 cog.outl("generated by cog")
2547 cog.outl("blah blah.")
2548 //]]]
2549 This line was newly
2550 generated by cog
2551 blah blah!
2552 //[[[end]]] (sum: qFQJguWta5)
2553 """,
2554 "cog3.txt": """\
2555 //[[[cog
2556 cog.outl("This line was newly")
2557 cog.outl("generated by cog")
2558 cog.outl("blah blah.")
2559 //]]]
2561 This line was newly
2562 generated by cog
2563 blah blah.
2564 //[[[end]]] (sum: qFQJguWta5)
2565 """,
2566 "cog4.txt": """\
2567 //[[[cog
2568 cog.outl("This line was newly")
2569 cog.outl("generated by cog")
2570 cog.outl("blah blah.")
2571 //]]]
2572 This line was newly
2573 generated by cog
2574 blah blah..
2575 //[[[end]]] (sum: qFQJguWta5)
2576 """,
2577 "cog5.txt": """\
2578 //[[[cog
2579 cog.outl("This line was newly")
2580 cog.outl("generated by cog")
2581 cog.outl("blah blah.")
2582 //]]]
2583 This line was newly
2584 generated by cog
2585 blah blah.
2586 extra
2587 //[[[end]]] (sum: qFQJguWta5)
2588 """,
2589 "cog6.txt": """\
2590 //[[[cog
2591 cog.outl("This line was newly")
2592 cog.outl("generated by cog")
2593 cog.outl("blah blah.")
2594 //]]]
2595 //[[[end]]] (sum: qFQJguWta5)
2596 """,
2597 }
2599 make_files(d)
2600 with self.assertRaisesRegex(
2601 CogError,
2602 r"^cog1.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2603 ):
2604 self.cog.callable_main(["argv0", "-c", "cog1.txt"])
2605 with self.assertRaisesRegex(
2606 CogError,
2607 r"^cog2.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2608 ):
2609 self.cog.callable_main(["argv0", "-c", "cog2.txt"])
2610 with self.assertRaisesRegex(
2611 CogError,
2612 r"^cog3.txt\(10\): Output has been edited! Delete old checksum to unprotect.$",
2613 ):
2614 self.cog.callable_main(["argv0", "-c", "cog3.txt"])
2615 with self.assertRaisesRegex(
2616 CogError,
2617 r"^cog4.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2618 ):
2619 self.cog.callable_main(["argv0", "-c", "cog4.txt"])
2620 with self.assertRaisesRegex(
2621 CogError,
2622 r"^cog5.txt\(10\): Output has been edited! Delete old checksum to unprotect.$",
2623 ):
2624 self.cog.callable_main(["argv0", "-c", "cog5.txt"])
2625 with self.assertRaisesRegex(
2626 CogError,
2627 r"^cog6.txt\(6\): Output has been edited! Delete old checksum to unprotect.$",
2628 ):
2629 self.cog.callable_main(["argv0", "-c", "cog6.txt"])
2631 def test_argv_isnt_modified(self):
2632 argv = ["argv0", "-v"]
2633 orig_argv = argv[:]
2634 self.cog.callable_main(argv)
2635 self.assertEqual(argv, orig_argv)
2638class CustomMarkerTests(TestCaseWithTempDir):
2639 def test_customer_markers(self):
2640 d = {
2641 "test.cog": """\
2642 //{{
2643 cog.outl("void %s();" % "MyFunction")
2644 //}}
2645 //{{end}}
2646 """,
2647 "test.out": """\
2648 //{{
2649 cog.outl("void %s();" % "MyFunction")
2650 //}}
2651 void MyFunction();
2652 //{{end}}
2653 """,
2654 }
2656 make_files(d)
2657 self.cog.callable_main(["argv0", "-r", "--markers={{ }} {{end}}", "test.cog"])
2658 self.assertFilesSame("test.cog", "test.out")
2660 def test_truly_wacky_markers(self):
2661 # Make sure the markers are properly re-escaped.
2662 d = {
2663 "test.cog": """\
2664 //**(
2665 cog.outl("void %s();" % "MyFunction")
2666 //**)
2667 //**(end)**
2668 """,
2669 "test.out": """\
2670 //**(
2671 cog.outl("void %s();" % "MyFunction")
2672 //**)
2673 void MyFunction();
2674 //**(end)**
2675 """,
2676 }
2678 make_files(d)
2679 self.cog.callable_main(
2680 ["argv0", "-r", "--markers=**( **) **(end)**", "test.cog"]
2681 )
2682 self.assertFilesSame("test.cog", "test.out")
2684 def test_change_just_one_marker(self):
2685 d = {
2686 "test.cog": """\
2687 //**(
2688 cog.outl("void %s();" % "MyFunction")
2689 //]]]
2690 //[[[end]]]
2691 """,
2692 "test.out": """\
2693 //**(
2694 cog.outl("void %s();" % "MyFunction")
2695 //]]]
2696 void MyFunction();
2697 //[[[end]]]
2698 """,
2699 }
2701 make_files(d)
2702 self.cog.callable_main(
2703 ["argv0", "-r", "--markers=**( ]]] [[[end]]]", "test.cog"]
2704 )
2705 self.assertFilesSame("test.cog", "test.out")
2708class BlakeTests(TestCaseWithTempDir):
2709 # Blake Winton's contributions.
2710 def test_delete_code(self):
2711 # -o sets the output file.
2712 d = {
2713 "test.cog": """\
2714 // This is my C++ file.
2715 //[[[cog
2716 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
2717 for fn in fnames:
2718 cog.outl("void %s();" % fn)
2719 //]]]
2720 Some Sample Code Here
2721 //[[[end]]]Data Data
2722 And Some More
2723 """,
2724 "test.out": """\
2725 // This is my C++ file.
2726 void DoSomething();
2727 void DoAnotherThing();
2728 void DoLastThing();
2729 And Some More
2730 """,
2731 }
2733 make_files(d)
2734 self.cog.callable_main(["argv0", "-d", "-o", "test.cogged", "test.cog"])
2735 self.assertFilesSame("test.cogged", "test.out")
2737 def test_delete_code_with_dash_r_fails(self):
2738 d = {
2739 "test.cog": """\
2740 // This is my C++ file.
2741 """
2742 }
2744 make_files(d)
2745 with self.assertRaisesRegex(
2746 CogUsageError,
2747 r"^Can't use -d with -r \(or you would delete all your source!\)$",
2748 ):
2749 self.cog.callable_main(["argv0", "-r", "-d", "test.cog"])
2751 def test_setting_globals(self):
2752 # Blake Winton contributed a way to set the globals that will be used in
2753 # processFile().
2754 d = {
2755 "test.cog": """\
2756 // This is my C++ file.
2757 //[[[cog
2758 for fn in fnames:
2759 cog.outl("void %s();" % fn)
2760 //]]]
2761 Some Sample Code Here
2762 //[[[end]]]""",
2763 "test.out": """\
2764 // This is my C++ file.
2765 void DoBlake();
2766 void DoWinton();
2767 void DoContribution();
2768 """,
2769 }
2771 make_files(d)
2772 globals = {}
2773 globals["fnames"] = ["DoBlake", "DoWinton", "DoContribution"]
2774 self.cog.options.delete_code = True
2775 self.cog.process_file("test.cog", "test.cogged", globals=globals)
2776 self.assertFilesSame("test.cogged", "test.out")
2779class ErrorCallTests(TestCaseWithTempDir):
2780 def test_error_call_has_no_traceback(self):
2781 # Test that cog.error() doesn't show a traceback.
2782 d = {
2783 "error.cog": """\
2784 //[[[cog
2785 cog.error("Something Bad!")
2786 //]]]
2787 //[[[end]]]
2788 """,
2789 }
2791 make_files(d)
2792 self.cog.main(["argv0", "-r", "error.cog"])
2793 output = self.output.getvalue()
2794 self.assertEqual(output, "Cogging error.cog\nError: Something Bad!\n")
2796 def test_real_error_has_traceback(self):
2797 # Test that a genuine error does show a traceback.
2798 d = {
2799 "error.cog": """\
2800 //[[[cog
2801 raise RuntimeError("Hey!")
2802 //]]]
2803 //[[[end]]]
2804 """,
2805 }
2807 make_files(d)
2808 self.cog.main(["argv0", "-r", "error.cog"])
2809 output = self.output.getvalue()
2810 msg = "Actual output:\n" + output
2811 self.assertTrue(
2812 output.startswith("Cogging error.cog\nTraceback (most recent"), msg
2813 )
2814 self.assertIn("RuntimeError: Hey!", output)
2817class HashHandlerTests(TestCase):
2818 """Test cases for HashHandler functionality."""
2820 def setUp(self):
2821 self.handler = HashHandler("[[[end]]]")
2823 def test_validate_hash_with_base64_mismatch(self):
2824 # Test the base64 validation branch with a mismatch
2825 line = "//[[[end]]] (sum: wronghas12)" # 10 chars to match regex
2826 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346"
2828 with self.assertRaises(ValueError) as cm:
2829 self.handler.validate_hash(line, expected_hash)
2830 self.assertEqual(
2831 str(cm.exception),
2832 "Output has been edited! Delete old checksum to unprotect.",
2833 )
2835 def test_validate_hash_with_base64_match(self):
2836 # Test the base64 validation branch with a match
2837 line = "//[[[end]]] (sum: qFQJguWta5)"
2838 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346"
2840 # Should not raise an exception
2841 result = self.handler.validate_hash(line, expected_hash)
2842 self.assertTrue(result)
2845# Things not yet tested:
2846# - A bad -w command (currently fails silently).