Coverage for cogapp/test_cogapp.py: 29.19%
905 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-13 08:29 -0400
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-13 08:29 -0400
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 usage, __version__, main
18from .hashhandler import HashHandler
19from .makefiles import make_files
20from .whiteutils import reindent_block
23class CogTestsInMemory(TestCase):
24 """Test cases for cogapp.Cog()"""
26 def test_no_cog(self):
27 strings = [
28 "",
29 " ",
30 " \t \t \tx",
31 "hello",
32 "the cat\nin the\nhat.",
33 "Horton\n\tHears A\n\t\tWho",
34 ]
35 for s in strings:
36 self.assertEqual(Cog().process_string(s), s)
38 def test_simple(self):
39 infile = """\
40 Some text.
41 //[[[cog
42 import cog
43 cog.outl("This is line one\\n")
44 cog.outl("This is line two")
45 //]]]
46 gobbledegook.
47 //[[[end]]]
48 epilogue.
49 """
51 outfile = """\
52 Some text.
53 //[[[cog
54 import cog
55 cog.outl("This is line one\\n")
56 cog.outl("This is line two")
57 //]]]
58 This is line one
60 This is line two
61 //[[[end]]]
62 epilogue.
63 """
65 self.assertEqual(Cog().process_string(infile), outfile)
67 def test_empty_cog(self):
68 # The cog clause can be totally empty. Not sure why you'd want it,
69 # but it works.
70 infile = """\
71 hello
72 //[[[cog
73 //]]]
74 //[[[end]]]
75 goodbye
76 """
78 infile = reindent_block(infile)
79 self.assertEqual(Cog().process_string(infile), infile)
81 def test_multiple_cogs(self):
82 # One file can have many cog chunks, even abutting each other.
83 infile = """\
84 //[[[cog
85 cog.out("chunk1")
86 //]]]
87 chunk1
88 //[[[end]]]
89 //[[[cog
90 cog.out("chunk2")
91 //]]]
92 chunk2
93 //[[[end]]]
94 between chunks
95 //[[[cog
96 cog.out("chunk3")
97 //]]]
98 chunk3
99 //[[[end]]]
100 """
102 infile = reindent_block(infile)
103 self.assertEqual(Cog().process_string(infile), infile)
105 def test_trim_blank_lines(self):
106 infile = """\
107 //[[[cog
108 cog.out("This is line one\\n", trimblanklines=True)
109 cog.out('''
110 This is line two
111 ''', dedent=True, trimblanklines=True)
112 cog.outl("This is line three", trimblanklines=True)
113 //]]]
114 This is line one
115 This is line two
116 This is line three
117 //[[[end]]]
118 """
120 infile = reindent_block(infile)
121 self.assertEqual(Cog().process_string(infile), infile)
123 def test_trim_empty_blank_lines(self):
124 infile = """\
125 //[[[cog
126 cog.out("This is line one\\n", trimblanklines=True)
127 cog.out('''
128 This is line two
129 ''', dedent=True, trimblanklines=True)
130 cog.out('', dedent=True, trimblanklines=True)
131 cog.outl("This is line three", trimblanklines=True)
132 //]]]
133 This is line one
134 This is line two
135 This is line three
136 //[[[end]]]
137 """
139 infile = reindent_block(infile)
140 self.assertEqual(Cog().process_string(infile), infile)
142 def test_trim_blank_lines_with_last_partial(self):
143 infile = """\
144 //[[[cog
145 cog.out("This is line one\\n", trimblanklines=True)
146 cog.out("\\nLine two\\nLine three", trimblanklines=True)
147 //]]]
148 This is line one
149 Line two
150 Line three
151 //[[[end]]]
152 """
154 infile = reindent_block(infile)
155 self.assertEqual(Cog().process_string(infile), infile)
157 def test_cog_out_dedent(self):
158 infile = """\
159 //[[[cog
160 cog.out("This is the first line\\n")
161 cog.out('''
162 This is dedent=True 1
163 This is dedent=True 2
164 ''', dedent=True, trimblanklines=True)
165 cog.out('''
166 This is dedent=False 1
167 This is dedent=False 2
168 ''', dedent=False, trimblanklines=True)
169 cog.out('''
170 This is dedent=default 1
171 This is dedent=default 2
172 ''', trimblanklines=True)
173 cog.out("This is the last line\\n")
174 //]]]
175 This is the first line
176 This is dedent=True 1
177 This is dedent=True 2
178 This is dedent=False 1
179 This is dedent=False 2
180 This is dedent=default 1
181 This is dedent=default 2
182 This is the last line
183 //[[[end]]]
184 """
186 infile = reindent_block(infile)
187 self.assertEqual(Cog().process_string(infile), infile)
189 def test22_end_of_line(self):
190 # In Python 2.2, this cog file was not parsing because the
191 # last line is indented but didn't end with a newline.
192 infile = """\
193 //[[[cog
194 import cog
195 for i in range(3):
196 cog.out("%d\\n" % i)
197 //]]]
198 0
199 1
200 2
201 //[[[end]]]
202 """
204 infile = reindent_block(infile)
205 self.assertEqual(Cog().process_string(infile), infile)
207 def test_indented_code(self):
208 infile = """\
209 first line
210 [[[cog
211 import cog
212 for i in range(3):
213 cog.out("xx%d\\n" % i)
214 ]]]
215 xx0
216 xx1
217 xx2
218 [[[end]]]
219 last line
220 """
222 infile = reindent_block(infile)
223 self.assertEqual(Cog().process_string(infile), infile)
225 def test_prefixed_code(self):
226 infile = """\
227 --[[[cog
228 --import cog
229 --for i in range(3):
230 -- cog.out("xx%d\\n" % i)
231 --]]]
232 xx0
233 xx1
234 xx2
235 --[[[end]]]
236 """
238 infile = reindent_block(infile)
239 self.assertEqual(Cog().process_string(infile), infile)
241 def test_prefixed_indented_code(self):
242 infile = """\
243 prologue
244 --[[[cog
245 -- import cog
246 -- for i in range(3):
247 -- cog.out("xy%d\\n" % i)
248 --]]]
249 xy0
250 xy1
251 xy2
252 --[[[end]]]
253 """
255 infile = reindent_block(infile)
256 self.assertEqual(Cog().process_string(infile), infile)
258 def test_bogus_prefix_match(self):
259 infile = """\
260 prologue
261 #[[[cog
262 import cog
263 # This comment should not be clobbered by removing the pound sign.
264 for i in range(3):
265 cog.out("xy%d\\n" % i)
266 #]]]
267 xy0
268 xy1
269 xy2
270 #[[[end]]]
271 """
273 infile = reindent_block(infile)
274 self.assertEqual(Cog().process_string(infile), infile)
276 def test_no_final_newline(self):
277 # If the cog'ed output has no final newline,
278 # it shouldn't eat up the cog terminator.
279 infile = """\
280 prologue
281 [[[cog
282 import cog
283 for i in range(3):
284 cog.out("%d" % i)
285 ]]]
286 012
287 [[[end]]]
288 epilogue
289 """
291 infile = reindent_block(infile)
292 self.assertEqual(Cog().process_string(infile), infile)
294 def test_no_output_at_all(self):
295 # If there is absolutely no cog output, that's ok.
296 infile = """\
297 prologue
298 [[[cog
299 i = 1
300 ]]]
301 [[[end]]]
302 epilogue
303 """
305 infile = reindent_block(infile)
306 self.assertEqual(Cog().process_string(infile), infile)
308 def test_purely_blank_line(self):
309 # If there is a blank line in the cog code with no whitespace
310 # prefix, that should be OK.
312 infile = """\
313 prologue
314 [[[cog
315 import sys
316 cog.out("Hello")
317 $
318 cog.out("There")
319 ]]]
320 HelloThere
321 [[[end]]]
322 epilogue
323 """
325 infile = reindent_block(infile.replace("$", ""))
326 self.assertEqual(Cog().process_string(infile), infile)
328 def test_empty_outl(self):
329 # Alexander Belchenko suggested the string argument to outl should
330 # be optional. Does it work?
332 infile = """\
333 prologue
334 [[[cog
335 cog.outl("x")
336 cog.outl()
337 cog.outl("y")
338 cog.out() # Also optional, a complete no-op.
339 cog.outl(trimblanklines=True)
340 cog.outl("z")
341 ]]]
342 x
344 y
346 z
347 [[[end]]]
348 epilogue
349 """
351 infile = reindent_block(infile)
352 self.assertEqual(Cog().process_string(infile), infile)
354 def test_first_line_num(self):
355 infile = """\
356 fooey
357 [[[cog
358 cog.outl("started at line number %d" % cog.firstLineNum)
359 ]]]
360 started at line number 2
361 [[[end]]]
362 blah blah
363 [[[cog
364 cog.outl("and again at line %d" % cog.firstLineNum)
365 ]]]
366 and again at line 8
367 [[[end]]]
368 """
370 infile = reindent_block(infile)
371 self.assertEqual(Cog().process_string(infile), infile)
373 def test_compact_one_line_code(self):
374 infile = """\
375 first line
376 hey: [[[cog cog.outl("hello %d" % (3*3*3*3)) ]]] looky!
377 get rid of this!
378 [[[end]]]
379 last line
380 """
382 outfile = """\
383 first line
384 hey: [[[cog cog.outl("hello %d" % (3*3*3*3)) ]]] looky!
385 hello 81
386 [[[end]]]
387 last line
388 """
390 infile = reindent_block(infile)
391 self.assertEqual(Cog().process_string(infile), reindent_block(outfile))
393 def test_inside_out_compact(self):
394 infile = """\
395 first line
396 hey?: ]]] what is this? [[[cog strange!
397 get rid of this!
398 [[[end]]]
399 last line
400 """
401 with self.assertRaisesRegex(
402 CogError, r"^infile.txt\(2\): Cog code markers inverted$"
403 ):
404 Cog().process_string(reindent_block(infile), "infile.txt")
406 def test_sharing_globals(self):
407 infile = """\
408 first line
409 hey: [[[cog s="hey there" ]]] looky!
410 [[[end]]]
411 more literal junk.
412 [[[cog cog.outl(s) ]]]
413 [[[end]]]
414 last line
415 """
417 outfile = """\
418 first line
419 hey: [[[cog s="hey there" ]]] looky!
420 [[[end]]]
421 more literal junk.
422 [[[cog cog.outl(s) ]]]
423 hey there
424 [[[end]]]
425 last line
426 """
428 infile = reindent_block(infile)
429 self.assertEqual(Cog().process_string(infile), reindent_block(outfile))
431 def test_assert_in_cog_code(self):
432 # Check that we can test assertions in cog code in the test framework.
433 infile = """\
434 [[[cog
435 assert 1 == 2, "Oops"
436 ]]]
437 [[[end]]]
438 """
439 infile = reindent_block(infile)
440 with self.assertRaisesRegex(CogUserException, "AssertionError: Oops"):
441 Cog().process_string(infile)
443 def test_cog_previous(self):
444 # Check that we can access the previous run's output.
445 infile = """\
446 [[[cog
447 assert cog.previous == "Hello there!\\n", "WTF??"
448 cog.out(cog.previous)
449 cog.outl("Ran again!")
450 ]]]
451 Hello there!
452 [[[end]]]
453 """
455 outfile = """\
456 [[[cog
457 assert cog.previous == "Hello there!\\n", "WTF??"
458 cog.out(cog.previous)
459 cog.outl("Ran again!")
460 ]]]
461 Hello there!
462 Ran again!
463 [[[end]]]
464 """
466 infile = reindent_block(infile)
467 self.assertEqual(Cog().process_string(infile), reindent_block(outfile))
470class CogOptionsTests(TestCase):
471 """Test the CogOptions class."""
473 def test_equality(self):
474 o = CogOptions()
475 p = CogOptions()
476 self.assertEqual(o, p)
477 o.parse_args(["-r"])
478 self.assertNotEqual(o, p)
479 p.parse_args(["-r"])
480 self.assertEqual(o, p)
482 def test_cloning(self):
483 o = CogOptions()
484 o.parse_args(["-I", "fooey", "-I", "booey", "-s", " /*x*/"])
485 p = o.clone()
486 self.assertEqual(o, p)
487 p.parse_args(["-I", "huey", "-D", "foo=quux"])
488 self.assertNotEqual(o, p)
489 q = CogOptions()
490 q.parse_args(
491 [
492 "-I",
493 "fooey",
494 "-I",
495 "booey",
496 "-s",
497 " /*x*/",
498 "-I",
499 "huey",
500 "-D",
501 "foo=quux",
502 ]
503 )
504 self.assertEqual(p, q)
506 def test_combining_flags(self):
507 # Single-character flags can be combined.
508 o = CogOptions()
509 o.parse_args(["-e", "-r", "-z"])
510 p = CogOptions()
511 p.parse_args(["-erz"])
512 self.assertEqual(o, p)
514 def test_markers(self):
515 o = CogOptions()
516 o._parse_markers("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("option -j not recognized", output)
813 with self.assertRaisesRegex(CogUsageError, r"^No files to process$"):
814 self.cog.callable_main(["argv0"])
815 with self.assertRaisesRegex(CogUsageError, r"^option -j not recognized$"):
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 self.assertEqual(usage, self.output.getvalue())
845 def test_dash_h(self):
846 # -h, --help, or -? anywhere on the command line should just print help.
847 self.produces_help("-h")
848 self.produces_help("--help")
849 self.produces_help("-?")
850 self.produces_help("fooey.txt -h")
851 self.produces_help("fooey.txt --help")
852 self.produces_help("-o -r @fooey.txt -? @booey.txt")
854 def test_dash_o_and_dash_r(self):
855 d = {
856 "cogfile.txt": """\
857 # Please run cog
858 """
859 }
861 make_files(d)
862 with self.assertRaisesRegex(
863 CogUsageError, r"^Can't use -o with -r \(they are opposites\)$"
864 ):
865 self.cog.callable_main(["argv0", "-o", "foo", "-r", "cogfile.txt"])
867 def test_dash_z(self):
868 d = {
869 "test.cog": """\
870 // This is my C++ file.
871 //[[[cog
872 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
873 for fn in fnames:
874 cog.outl("void %s();" % fn)
875 //]]]
876 """,
877 "test.out": """\
878 // This is my C++ file.
879 //[[[cog
880 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
881 for fn in fnames:
882 cog.outl("void %s();" % fn)
883 //]]]
884 void DoSomething();
885 void DoAnotherThing();
886 void DoLastThing();
887 """,
888 }
890 make_files(d)
891 with self.assertRaisesRegex(
892 CogError, r"^test.cog\(6\): Missing '\[\[\[end\]\]\]' before end of file.$"
893 ):
894 self.cog.callable_main(["argv0", "-r", "test.cog"])
895 self.new_cog()
896 self.cog.callable_main(["argv0", "-r", "-z", "test.cog"])
897 self.assertFilesSame("test.cog", "test.out")
899 def test_bad_dash_d(self):
900 with self.assertRaisesRegex(CogUsageError, r"^-D takes a name=value argument$"):
901 self.cog.callable_main(["argv0", "-Dfooey", "cog.txt"])
902 with self.assertRaisesRegex(CogUsageError, r"^-D takes a name=value argument$"):
903 self.cog.callable_main(["argv0", "-D", "fooey", "cog.txt"])
905 def test_bad_markers(self):
906 with self.assertRaisesRegex(
907 CogUsageError,
908 r"^--markers requires 3 values separated by spaces, could not parse 'X'$",
909 ):
910 self.cog.callable_main(["argv0", "--markers=X"])
911 with self.assertRaisesRegex(
912 CogUsageError,
913 r"^--markers requires 3 values separated by spaces, could not parse 'A B C D'$",
914 ):
915 self.cog.callable_main(["argv0", "--markers=A B C D"])
918class TestMain(TestCaseWithTempDir):
919 def setUp(self):
920 super().setUp()
921 self.old_argv = sys.argv[:]
922 self.old_stderr = sys.stderr
923 sys.stderr = io.StringIO()
925 def tearDown(self):
926 sys.stderr = self.old_stderr
927 sys.argv = self.old_argv
928 sys.modules.pop("mycode", None)
929 super().tearDown()
931 def test_main_function(self):
932 sys.argv = ["argv0", "-Z"]
933 ret = main()
934 self.assertEqual(ret, 2)
935 stderr = sys.stderr.getvalue()
936 self.assertEqual(stderr, "option -Z not recognized\n(for help use --help)\n")
938 files = {
939 "test.cog": """\
940 //[[[cog
941 def func():
942 import mycode
943 mycode.boom()
944 //]]]
945 //[[[end]]]
946 -----
947 //[[[cog
948 func()
949 //]]]
950 //[[[end]]]
951 """,
952 "mycode.py": """\
953 def boom():
954 [][0]
955 """,
956 }
958 def test_error_report(self):
959 self.check_error_report()
961 def test_error_report_with_prologue(self):
962 self.check_error_report("-p", "#1\n#2")
964 def check_error_report(self, *args):
965 """Check that the error report is right."""
966 make_files(self.files)
967 sys.argv = ["argv0"] + list(args) + ["-r", "test.cog"]
968 main()
969 expected = reindent_block("""\
970 Traceback (most recent call last):
971 File "test.cog", line 9, in <module>
972 func()
973 File "test.cog", line 4, in func
974 mycode.boom()
975 File "MYCODE", line 2, in boom
976 [][0]
977 IndexError: list index out of range
978 """)
979 expected = expected.replace("MYCODE", os.path.abspath("mycode.py"))
980 assert expected == sys.stderr.getvalue()
982 def test_error_in_prologue(self):
983 make_files(self.files)
984 sys.argv = ["argv0", "-p", "import mycode; mycode.boom()", "-r", "test.cog"]
985 main()
986 expected = reindent_block("""\
987 Traceback (most recent call last):
988 File "<prologue>", line 1, in <module>
989 import mycode; mycode.boom()
990 File "MYCODE", line 2, in boom
991 [][0]
992 IndexError: list index out of range
993 """)
994 expected = expected.replace("MYCODE", os.path.abspath("mycode.py"))
995 assert expected == sys.stderr.getvalue()
998class TestFileHandling(TestCaseWithTempDir):
999 def test_simple(self):
1000 d = {
1001 "test.cog": """\
1002 // This is my C++ file.
1003 //[[[cog
1004 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1005 for fn in fnames:
1006 cog.outl("void %s();" % fn)
1007 //]]]
1008 //[[[end]]]
1009 """,
1010 "test.out": """\
1011 // This is my C++ file.
1012 //[[[cog
1013 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1014 for fn in fnames:
1015 cog.outl("void %s();" % fn)
1016 //]]]
1017 void DoSomething();
1018 void DoAnotherThing();
1019 void DoLastThing();
1020 //[[[end]]]
1021 """,
1022 }
1024 make_files(d)
1025 self.cog.callable_main(["argv0", "-r", "test.cog"])
1026 self.assertFilesSame("test.cog", "test.out")
1027 output = self.output.getvalue()
1028 self.assertIn("(changed)", output)
1030 def test_print_output(self):
1031 d = {
1032 "test.cog": """\
1033 // This is my C++ file.
1034 //[[[cog
1035 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1036 for fn in fnames:
1037 print("void %s();" % fn)
1038 //]]]
1039 //[[[end]]]
1040 """,
1041 "test.out": """\
1042 // This is my C++ file.
1043 //[[[cog
1044 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1045 for fn in fnames:
1046 print("void %s();" % fn)
1047 //]]]
1048 void DoSomething();
1049 void DoAnotherThing();
1050 void DoLastThing();
1051 //[[[end]]]
1052 """,
1053 }
1055 make_files(d)
1056 self.cog.callable_main(["argv0", "-rP", "test.cog"])
1057 self.assertFilesSame("test.cog", "test.out")
1058 output = self.output.getvalue()
1059 self.assertIn("(changed)", output)
1061 def test_wildcards(self):
1062 d = {
1063 "test.cog": """\
1064 // This is my C++ file.
1065 //[[[cog
1066 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1067 for fn in fnames:
1068 cog.outl("void %s();" % fn)
1069 //]]]
1070 //[[[end]]]
1071 """,
1072 "test2.cog": """\
1073 // This is my C++ file.
1074 //[[[cog
1075 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1076 for fn in fnames:
1077 cog.outl("void %s();" % fn)
1078 //]]]
1079 //[[[end]]]
1080 """,
1081 "test.out": """\
1082 // This is my C++ file.
1083 //[[[cog
1084 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1085 for fn in fnames:
1086 cog.outl("void %s();" % fn)
1087 //]]]
1088 void DoSomething();
1089 void DoAnotherThing();
1090 void DoLastThing();
1091 //[[[end]]]
1092 """,
1093 "not_this_one.cog": """\
1094 // This is my C++ file.
1095 //[[[cog
1096 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1097 for fn in fnames:
1098 cog.outl("void %s();" % fn)
1099 //]]]
1100 //[[[end]]]
1101 """,
1102 "not_this_one.out": """\
1103 // This is my C++ file.
1104 //[[[cog
1105 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1106 for fn in fnames:
1107 cog.outl("void %s();" % fn)
1108 //]]]
1109 //[[[end]]]
1110 """,
1111 }
1113 make_files(d)
1114 self.cog.callable_main(["argv0", "-r", "t*.cog"])
1115 self.assertFilesSame("test.cog", "test.out")
1116 self.assertFilesSame("test2.cog", "test.out")
1117 self.assertFilesSame("not_this_one.cog", "not_this_one.out")
1118 output = self.output.getvalue()
1119 self.assertIn("(changed)", output)
1121 def test_output_file(self):
1122 # -o sets the output file.
1123 d = {
1124 "test.cog": """\
1125 // This is my C++ file.
1126 //[[[cog
1127 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1128 for fn in fnames:
1129 cog.outl("void %s();" % fn)
1130 //]]]
1131 //[[[end]]]
1132 """,
1133 "test.out": """\
1134 // This is my C++ file.
1135 //[[[cog
1136 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
1137 for fn in fnames:
1138 cog.outl("void %s();" % fn)
1139 //]]]
1140 void DoSomething();
1141 void DoAnotherThing();
1142 void DoLastThing();
1143 //[[[end]]]
1144 """,
1145 }
1147 make_files(d)
1148 self.cog.callable_main(["argv0", "-o", "in/a/dir/test.cogged", "test.cog"])
1149 self.assertFilesSame("in/a/dir/test.cogged", "test.out")
1151 def test_at_file(self):
1152 d = {
1153 "one.cog": """\
1154 //[[[cog
1155 cog.outl("hello world")
1156 //]]]
1157 //[[[end]]]
1158 """,
1159 "one.out": """\
1160 //[[[cog
1161 cog.outl("hello world")
1162 //]]]
1163 hello world
1164 //[[[end]]]
1165 """,
1166 "two.cog": """\
1167 //[[[cog
1168 cog.outl("goodbye cruel world")
1169 //]]]
1170 //[[[end]]]
1171 """,
1172 "two.out": """\
1173 //[[[cog
1174 cog.outl("goodbye cruel world")
1175 //]]]
1176 goodbye cruel world
1177 //[[[end]]]
1178 """,
1179 "cogfiles.txt": """\
1180 # Please run cog
1181 one.cog
1183 two.cog
1184 """,
1185 }
1187 make_files(d)
1188 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1189 self.assertFilesSame("one.cog", "one.out")
1190 self.assertFilesSame("two.cog", "two.out")
1191 output = self.output.getvalue()
1192 self.assertIn("(changed)", output)
1194 def test_nested_at_file(self):
1195 d = {
1196 "one.cog": """\
1197 //[[[cog
1198 cog.outl("hello world")
1199 //]]]
1200 //[[[end]]]
1201 """,
1202 "one.out": """\
1203 //[[[cog
1204 cog.outl("hello world")
1205 //]]]
1206 hello world
1207 //[[[end]]]
1208 """,
1209 "two.cog": """\
1210 //[[[cog
1211 cog.outl("goodbye cruel world")
1212 //]]]
1213 //[[[end]]]
1214 """,
1215 "two.out": """\
1216 //[[[cog
1217 cog.outl("goodbye cruel world")
1218 //]]]
1219 goodbye cruel world
1220 //[[[end]]]
1221 """,
1222 "cogfiles.txt": """\
1223 # Please run cog
1224 one.cog
1225 @cogfiles2.txt
1226 """,
1227 "cogfiles2.txt": """\
1228 # This one too, please.
1229 two.cog
1230 """,
1231 }
1233 make_files(d)
1234 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1235 self.assertFilesSame("one.cog", "one.out")
1236 self.assertFilesSame("two.cog", "two.out")
1237 output = self.output.getvalue()
1238 self.assertIn("(changed)", output)
1240 def test_at_file_with_args(self):
1241 d = {
1242 "both.cog": """\
1243 //[[[cog
1244 cog.outl("one: %s" % ('one' in globals()))
1245 cog.outl("two: %s" % ('two' in globals()))
1246 //]]]
1247 //[[[end]]]
1248 """,
1249 "one.out": """\
1250 //[[[cog
1251 cog.outl("one: %s" % ('one' in globals()))
1252 cog.outl("two: %s" % ('two' in globals()))
1253 //]]]
1254 one: True // ONE
1255 two: False // ONE
1256 //[[[end]]]
1257 """,
1258 "two.out": """\
1259 //[[[cog
1260 cog.outl("one: %s" % ('one' in globals()))
1261 cog.outl("two: %s" % ('two' in globals()))
1262 //]]]
1263 one: False // TWO
1264 two: True // TWO
1265 //[[[end]]]
1266 """,
1267 "cogfiles.txt": """\
1268 # Please run cog
1269 both.cog -o in/a/dir/both.one -s ' // ONE' -D one=x
1270 both.cog -o in/a/dir/both.two -s ' // TWO' -D two=x
1271 """,
1272 }
1274 make_files(d)
1275 self.cog.callable_main(["argv0", "@cogfiles.txt"])
1276 self.assertFilesSame("in/a/dir/both.one", "one.out")
1277 self.assertFilesSame("in/a/dir/both.two", "two.out")
1279 def test_at_file_with_bad_arg_combo(self):
1280 d = {
1281 "both.cog": """\
1282 //[[[cog
1283 cog.outl("one: %s" % ('one' in globals()))
1284 cog.outl("two: %s" % ('two' in globals()))
1285 //]]]
1286 //[[[end]]]
1287 """,
1288 "cogfiles.txt": """\
1289 # Please run cog
1290 both.cog
1291 both.cog -d # This is bad: -r and -d
1292 """,
1293 }
1295 make_files(d)
1296 with self.assertRaisesRegex(
1297 CogUsageError,
1298 r"^Can't use -d with -r \(or you would delete all your source!\)$",
1299 ):
1300 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1302 def test_at_file_with_tricky_filenames(self):
1303 def fix_backslashes(files_txt):
1304 """Make the contents of a files.txt sensitive to the platform."""
1305 if sys.platform != "win32":
1306 files_txt = files_txt.replace("\\", "/")
1307 return files_txt
1309 d = {
1310 "one 1.cog": """\
1311 //[[[cog cog.outl("hello world") ]]]
1312 """,
1313 "one.out": """\
1314 //[[[cog cog.outl("hello world") ]]]
1315 hello world //xxx
1316 """,
1317 "subdir": {
1318 "subback.cog": """\
1319 //[[[cog cog.outl("down deep with backslashes") ]]]
1320 """,
1321 "subfwd.cog": """\
1322 //[[[cog cog.outl("down deep with slashes") ]]]
1323 """,
1324 },
1325 "subback.out": """\
1326 //[[[cog cog.outl("down deep with backslashes") ]]]
1327 down deep with backslashes //yyy
1328 """,
1329 "subfwd.out": """\
1330 //[[[cog cog.outl("down deep with slashes") ]]]
1331 down deep with slashes //zzz
1332 """,
1333 "cogfiles.txt": fix_backslashes("""\
1334 # Please run cog
1335 'one 1.cog' -s ' //xxx'
1336 subdir\\subback.cog -s ' //yyy'
1337 subdir/subfwd.cog -s ' //zzz'
1338 """),
1339 }
1341 make_files(d)
1342 self.cog.callable_main(["argv0", "-z", "-r", "@cogfiles.txt"])
1343 self.assertFilesSame("one 1.cog", "one.out")
1344 self.assertFilesSame("subdir/subback.cog", "subback.out")
1345 self.assertFilesSame("subdir/subfwd.cog", "subfwd.out")
1347 def test_amp_file(self):
1348 d = {
1349 "code": {
1350 "files_to_cog": """\
1351 # A locally resolved file name.
1352 test.cog
1353 """,
1354 "test.cog": """\
1355 //[[[cog
1356 import myampsubmodule
1357 //]]]
1358 //[[[end]]]
1359 """,
1360 "test.out": """\
1361 //[[[cog
1362 import myampsubmodule
1363 //]]]
1364 Hello from myampsubmodule
1365 //[[[end]]]
1366 """,
1367 "myampsubmodule.py": """\
1368 import cog
1369 cog.outl("Hello from myampsubmodule")
1370 """,
1371 }
1372 }
1374 make_files(d)
1375 print(os.path.abspath("code/test.out"))
1376 self.cog.callable_main(["argv0", "-r", "&code/files_to_cog"])
1377 self.assertFilesSame("code/test.cog", "code/test.out")
1379 def run_with_verbosity(self, verbosity):
1380 d = {
1381 "unchanged.cog": """\
1382 //[[[cog
1383 cog.outl("hello world")
1384 //]]]
1385 hello world
1386 //[[[end]]]
1387 """,
1388 "changed.cog": """\
1389 //[[[cog
1390 cog.outl("goodbye cruel world")
1391 //]]]
1392 //[[[end]]]
1393 """,
1394 "cogfiles.txt": """\
1395 unchanged.cog
1396 changed.cog
1397 """,
1398 }
1400 make_files(d)
1401 self.cog.callable_main(
1402 ["argv0", "-r", "--verbosity=" + verbosity, "@cogfiles.txt"]
1403 )
1404 output = self.output.getvalue()
1405 return output
1407 def test_verbosity0(self):
1408 output = self.run_with_verbosity("0")
1409 self.assertEqual(output, "")
1411 def test_verbosity1(self):
1412 output = self.run_with_verbosity("1")
1413 self.assertEqual(output, "Cogging changed.cog (changed)\n")
1415 def test_verbosity2(self):
1416 output = self.run_with_verbosity("2")
1417 self.assertEqual(
1418 output, "Cogging unchanged.cog\nCogging changed.cog (changed)\n"
1419 )
1421 def test_change_dir(self):
1422 # The code can change directories, cog will move us back.
1423 d = {
1424 "sub": {
1425 "data.txt": "Hello!",
1426 },
1427 "test.cog": """\
1428 //[[[cog
1429 import os
1430 os.chdir("sub")
1431 cog.outl(open("data.txt").read())
1432 //]]]
1433 //[[[end]]]
1434 """,
1435 "test.out": """\
1436 //[[[cog
1437 import os
1438 os.chdir("sub")
1439 cog.outl(open("data.txt").read())
1440 //]]]
1441 Hello!
1442 //[[[end]]]
1443 """,
1444 }
1446 make_files(d)
1447 self.cog.callable_main(["argv0", "-r", "test.cog"])
1448 self.assertFilesSame("test.cog", "test.out")
1449 output = self.output.getvalue()
1450 self.assertIn("(changed)", output)
1453class CogTestLineEndings(TestCaseWithTempDir):
1454 """Tests for -U option (force LF line-endings in output)."""
1456 lines_in = [
1457 "Some text.",
1458 "//[[[cog",
1459 'cog.outl("Cog text")',
1460 "//]]]",
1461 "gobbledegook.",
1462 "//[[[end]]]",
1463 "epilogue.",
1464 "",
1465 ]
1467 lines_out = [
1468 "Some text.",
1469 "//[[[cog",
1470 'cog.outl("Cog text")',
1471 "//]]]",
1472 "Cog text",
1473 "//[[[end]]]",
1474 "epilogue.",
1475 "",
1476 ]
1478 def test_output_native_eol(self):
1479 make_files({"infile": "\n".join(self.lines_in)})
1480 self.cog.callable_main(["argv0", "-o", "outfile", "infile"])
1481 self.assertFileContent("outfile", os.linesep.join(self.lines_out))
1483 def test_output_lf_eol(self):
1484 make_files({"infile": "\n".join(self.lines_in)})
1485 self.cog.callable_main(["argv0", "-U", "-o", "outfile", "infile"])
1486 self.assertFileContent("outfile", "\n".join(self.lines_out))
1488 def test_replace_native_eol(self):
1489 make_files({"test.cog": "\n".join(self.lines_in)})
1490 self.cog.callable_main(["argv0", "-r", "test.cog"])
1491 self.assertFileContent("test.cog", os.linesep.join(self.lines_out))
1493 def test_replace_lf_eol(self):
1494 make_files({"test.cog": "\n".join(self.lines_in)})
1495 self.cog.callable_main(["argv0", "-U", "-r", "test.cog"])
1496 self.assertFileContent("test.cog", "\n".join(self.lines_out))
1499class CogTestCharacterEncoding(TestCaseWithTempDir):
1500 def test_simple(self):
1501 d = {
1502 "test.cog": b"""\
1503 // This is my C++ file.
1504 //[[[cog
1505 cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)")
1506 //]]]
1507 //[[[end]]]
1508 """,
1509 "test.out": b"""\
1510 // This is my C++ file.
1511 //[[[cog
1512 cog.outl("// Unicode: \xe1\x88\xb4 (U+1234)")
1513 //]]]
1514 // Unicode: \xe1\x88\xb4 (U+1234)
1515 //[[[end]]]
1516 """.replace(b"\n", os.linesep.encode()),
1517 }
1519 make_files(d)
1520 self.cog.callable_main(["argv0", "-r", "test.cog"])
1521 self.assertFilesSame("test.cog", "test.out")
1522 output = self.output.getvalue()
1523 self.assertIn("(changed)", output)
1525 def test_file_encoding_option(self):
1526 d = {
1527 "test.cog": b"""\
1528 // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows
1529 //[[[cog
1530 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")
1531 //]]]
1532 //[[[end]]]
1533 """,
1534 "test.out": b"""\
1535 // \xca\xee\xe4\xe8\xf0\xe2\xea\xe0 Windows
1536 //[[[cog
1537 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")
1538 //]]]
1539 \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
1540 //[[[end]]]
1541 """.replace(b"\n", os.linesep.encode()),
1542 }
1544 make_files(d)
1545 self.cog.callable_main(["argv0", "-n", "cp1251", "-r", "test.cog"])
1546 self.assertFilesSame("test.cog", "test.out")
1547 output = self.output.getvalue()
1548 self.assertIn("(changed)", output)
1551class TestCaseWithImports(TestCaseWithTempDir):
1552 """Automatic resetting of sys.modules for tests that import modules.
1554 When running tests which import modules, the sys.modules list
1555 leaks from one test to the next. This test case class scrubs
1556 the list after each run to keep the tests isolated from each other.
1558 """
1560 def setUp(self):
1561 super().setUp()
1562 self.sysmodulekeys = list(sys.modules)
1564 def tearDown(self):
1565 modstoscrub = [
1566 modname for modname in sys.modules if modname not in self.sysmodulekeys
1567 ]
1568 for modname in modstoscrub:
1569 del sys.modules[modname]
1570 super().tearDown()
1573class CogIncludeTests(TestCaseWithImports):
1574 dincludes = {
1575 "test.cog": """\
1576 //[[[cog
1577 import mymodule
1578 //]]]
1579 //[[[end]]]
1580 """,
1581 "test.out": """\
1582 //[[[cog
1583 import mymodule
1584 //]]]
1585 Hello from mymodule
1586 //[[[end]]]
1587 """,
1588 "test2.out": """\
1589 //[[[cog
1590 import mymodule
1591 //]]]
1592 Hello from mymodule in inc2
1593 //[[[end]]]
1594 """,
1595 "include": {
1596 "mymodule.py": """\
1597 import cog
1598 cog.outl("Hello from mymodule")
1599 """
1600 },
1601 "inc2": {
1602 "mymodule.py": """\
1603 import cog
1604 cog.outl("Hello from mymodule in inc2")
1605 """
1606 },
1607 "inc3": {
1608 "someothermodule.py": """\
1609 import cog
1610 cog.outl("This is some other module.")
1611 """
1612 },
1613 }
1615 def test_need_include_path(self):
1616 # Try it without the -I, to see that an ImportError happens.
1617 make_files(self.dincludes)
1618 msg = "(ImportError|ModuleNotFoundError): No module named '?mymodule'?"
1619 with self.assertRaisesRegex(CogUserException, msg):
1620 self.cog.callable_main(["argv0", "-r", "test.cog"])
1622 def test_include_path(self):
1623 # Test that -I adds include directories properly.
1624 make_files(self.dincludes)
1625 self.cog.callable_main(["argv0", "-r", "-I", "include", "test.cog"])
1626 self.assertFilesSame("test.cog", "test.out")
1628 def test_two_include_paths(self):
1629 # Test that two -I's add include directories properly.
1630 make_files(self.dincludes)
1631 self.cog.callable_main(
1632 ["argv0", "-r", "-I", "include", "-I", "inc2", "test.cog"]
1633 )
1634 self.assertFilesSame("test.cog", "test.out")
1636 def test_two_include_paths2(self):
1637 # Test that two -I's add include directories properly.
1638 make_files(self.dincludes)
1639 self.cog.callable_main(
1640 ["argv0", "-r", "-I", "inc2", "-I", "include", "test.cog"]
1641 )
1642 self.assertFilesSame("test.cog", "test2.out")
1644 def test_useless_include_path(self):
1645 # Test that the search will continue past the first directory.
1646 make_files(self.dincludes)
1647 self.cog.callable_main(
1648 ["argv0", "-r", "-I", "inc3", "-I", "include", "test.cog"]
1649 )
1650 self.assertFilesSame("test.cog", "test.out")
1652 def test_sys_path_is_unchanged(self):
1653 d = {
1654 "bad.cog": """\
1655 //[[[cog cog.error("Oh no!") ]]]
1656 //[[[end]]]
1657 """,
1658 "good.cog": """\
1659 //[[[cog cog.outl("Oh yes!") ]]]
1660 //[[[end]]]
1661 """,
1662 }
1664 make_files(d)
1665 # Is it unchanged just by creating a cog engine?
1666 oldsyspath = sys.path[:]
1667 self.new_cog()
1668 self.assertEqual(oldsyspath, sys.path)
1669 # Is it unchanged for a successful run?
1670 self.new_cog()
1671 self.cog.callable_main(["argv0", "-r", "good.cog"])
1672 self.assertEqual(oldsyspath, sys.path)
1673 # Is it unchanged for a successful run with includes?
1674 self.new_cog()
1675 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "good.cog"])
1676 self.assertEqual(oldsyspath, sys.path)
1677 # Is it unchanged for a successful run with two includes?
1678 self.new_cog()
1679 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "-I", "quux", "good.cog"])
1680 self.assertEqual(oldsyspath, sys.path)
1681 # Is it unchanged for a failed run?
1682 self.new_cog()
1683 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1684 self.cog.callable_main(["argv0", "-r", "bad.cog"])
1685 self.assertEqual(oldsyspath, sys.path)
1686 # Is it unchanged for a failed run with includes?
1687 self.new_cog()
1688 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1689 self.cog.callable_main(["argv0", "-r", "-I", "xyzzy", "bad.cog"])
1690 self.assertEqual(oldsyspath, sys.path)
1691 # Is it unchanged for a failed run with two includes?
1692 self.new_cog()
1693 with self.assertRaisesRegex(CogError, r"^Oh no!$"):
1694 self.cog.callable_main(
1695 ["argv0", "-r", "-I", "xyzzy", "-I", "quux", "bad.cog"]
1696 )
1697 self.assertEqual(oldsyspath, sys.path)
1699 def test_sub_directories(self):
1700 # Test that relative paths on the command line work, with includes.
1702 d = {
1703 "code": {
1704 "test.cog": """\
1705 //[[[cog
1706 import mysubmodule
1707 //]]]
1708 //[[[end]]]
1709 """,
1710 "test.out": """\
1711 //[[[cog
1712 import mysubmodule
1713 //]]]
1714 Hello from mysubmodule
1715 //[[[end]]]
1716 """,
1717 "mysubmodule.py": """\
1718 import cog
1719 cog.outl("Hello from mysubmodule")
1720 """,
1721 }
1722 }
1724 make_files(d)
1725 # We should be able to invoke cog without the -I switch, and it will
1726 # auto-include the current directory
1727 self.cog.callable_main(["argv0", "-r", "code/test.cog"])
1728 self.assertFilesSame("code/test.cog", "code/test.out")
1731class CogTestsInFiles(TestCaseWithTempDir):
1732 def test_warn_if_no_cog_code(self):
1733 # Test that the -e switch warns if there is no Cog code.
1734 d = {
1735 "with.cog": """\
1736 //[[[cog
1737 cog.outl("hello world")
1738 //]]]
1739 hello world
1740 //[[[end]]]
1741 """,
1742 "without.cog": """\
1743 There's no cog
1744 code in this file.
1745 """,
1746 }
1748 make_files(d)
1749 self.cog.callable_main(["argv0", "-e", "with.cog"])
1750 output = self.output.getvalue()
1751 self.assertNotIn("Warning", output)
1752 self.new_cog()
1753 self.cog.callable_main(["argv0", "-e", "without.cog"])
1754 output = self.output.getvalue()
1755 self.assertIn("Warning: no cog code found in without.cog", output)
1756 self.new_cog()
1757 self.cog.callable_main(["argv0", "without.cog"])
1758 output = self.output.getvalue()
1759 self.assertNotIn("Warning", output)
1761 def test_file_name_props(self):
1762 d = {
1763 "cog1.txt": """\
1764 //[[[cog
1765 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1766 //]]]
1767 this is cog1.txt in, cog1.txt out
1768 [[[end]]]
1769 """,
1770 "cog1.out": """\
1771 //[[[cog
1772 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1773 //]]]
1774 This is cog1.txt in, cog1.txt out
1775 [[[end]]]
1776 """,
1777 "cog1out.out": """\
1778 //[[[cog
1779 cog.outl("This is %s in, %s out" % (cog.inFile, cog.outFile))
1780 //]]]
1781 This is cog1.txt in, cog1out.txt out
1782 [[[end]]]
1783 """,
1784 }
1786 make_files(d)
1787 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
1788 self.assertFilesSame("cog1.txt", "cog1.out")
1789 self.new_cog()
1790 self.cog.callable_main(["argv0", "-o", "cog1out.txt", "cog1.txt"])
1791 self.assertFilesSame("cog1out.txt", "cog1out.out")
1793 def test_globals_dont_cross_files(self):
1794 # Make sure that global values don't get shared between files.
1795 d = {
1796 "one.cog": """\
1797 //[[[cog s = "This was set in one.cog" ]]]
1798 //[[[end]]]
1799 //[[[cog cog.outl(s) ]]]
1800 //[[[end]]]
1801 """,
1802 "one.out": """\
1803 //[[[cog s = "This was set in one.cog" ]]]
1804 //[[[end]]]
1805 //[[[cog cog.outl(s) ]]]
1806 This was set in one.cog
1807 //[[[end]]]
1808 """,
1809 "two.cog": """\
1810 //[[[cog
1811 try:
1812 cog.outl(s)
1813 except NameError:
1814 cog.outl("s isn't set!")
1815 //]]]
1816 //[[[end]]]
1817 """,
1818 "two.out": """\
1819 //[[[cog
1820 try:
1821 cog.outl(s)
1822 except NameError:
1823 cog.outl("s isn't set!")
1824 //]]]
1825 s isn't set!
1826 //[[[end]]]
1827 """,
1828 "cogfiles.txt": """\
1829 # Please run cog
1830 one.cog
1832 two.cog
1833 """,
1834 }
1836 make_files(d)
1837 self.cog.callable_main(["argv0", "-r", "@cogfiles.txt"])
1838 self.assertFilesSame("one.cog", "one.out")
1839 self.assertFilesSame("two.cog", "two.out")
1840 output = self.output.getvalue()
1841 self.assertIn("(changed)", output)
1843 def test_remove_generated_output(self):
1844 d = {
1845 "cog1.txt": """\
1846 //[[[cog
1847 cog.outl("This line was generated.")
1848 //]]]
1849 This line was generated.
1850 //[[[end]]]
1851 This line was not.
1852 """,
1853 "cog1.out": """\
1854 //[[[cog
1855 cog.outl("This line was generated.")
1856 //]]]
1857 //[[[end]]]
1858 This line was not.
1859 """,
1860 "cog1.out2": """\
1861 //[[[cog
1862 cog.outl("This line was generated.")
1863 //]]]
1864 This line was generated.
1865 //[[[end]]]
1866 This line was not.
1867 """,
1868 }
1870 make_files(d)
1871 # Remove generated output.
1872 self.cog.callable_main(["argv0", "-r", "-x", "cog1.txt"])
1873 self.assertFilesSame("cog1.txt", "cog1.out")
1874 self.new_cog()
1875 # Regenerate the generated output.
1876 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
1877 self.assertFilesSame("cog1.txt", "cog1.out2")
1878 self.new_cog()
1879 # Remove the generated output again.
1880 self.cog.callable_main(["argv0", "-r", "-x", "cog1.txt"])
1881 self.assertFilesSame("cog1.txt", "cog1.out")
1883 def test_msg_call(self):
1884 infile = """\
1885 #[[[cog
1886 cog.msg("Hello there!")
1887 #]]]
1888 #[[[end]]]
1889 """
1890 infile = reindent_block(infile)
1891 self.assertEqual(self.cog.process_string(infile), infile)
1892 output = self.output.getvalue()
1893 self.assertEqual(output, "Message: Hello there!\n")
1895 def test_error_message_has_no_traceback(self):
1896 # Test that a Cog error is printed to stderr with no traceback.
1898 d = {
1899 "cog1.txt": """\
1900 //[[[cog
1901 cog.outl("This line was newly")
1902 cog.outl("generated by cog")
1903 cog.outl("blah blah.")
1904 //]]]
1905 Xhis line was newly
1906 generated by cog
1907 blah blah.
1908 //[[[end]]] (sum: qFQJguWta5)
1909 """,
1910 }
1912 make_files(d)
1913 stderr = io.StringIO()
1914 self.cog.set_output(stderr=stderr)
1915 self.cog.main(["argv0", "-c", "-r", "cog1.txt"])
1916 self.assertEqual(self.output.getvalue(), "Cogging cog1.txt\n")
1917 self.assertEqual(
1918 stderr.getvalue(),
1919 "cog1.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
1920 )
1922 def test_dash_d(self):
1923 d = {
1924 "test.cog": """\
1925 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1926 --[[[end]]]
1927 """,
1928 "test.kablooey": """\
1929 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1930 Defined fooey as kablooey
1931 --[[[end]]]
1932 """,
1933 "test.einstein": """\
1934 --[[[cog cog.outl("Defined fooey as " + fooey) ]]]
1935 Defined fooey as e=mc2
1936 --[[[end]]]
1937 """,
1938 }
1940 make_files(d)
1941 self.cog.callable_main(["argv0", "-r", "-D", "fooey=kablooey", "test.cog"])
1942 self.assertFilesSame("test.cog", "test.kablooey")
1943 make_files(d)
1944 self.cog.callable_main(["argv0", "-r", "-Dfooey=kablooey", "test.cog"])
1945 self.assertFilesSame("test.cog", "test.kablooey")
1946 make_files(d)
1947 self.cog.callable_main(["argv0", "-r", "-Dfooey=e=mc2", "test.cog"])
1948 self.assertFilesSame("test.cog", "test.einstein")
1949 make_files(d)
1950 self.cog.callable_main(
1951 ["argv0", "-r", "-Dbar=quux", "-Dfooey=kablooey", "test.cog"]
1952 )
1953 self.assertFilesSame("test.cog", "test.kablooey")
1954 make_files(d)
1955 self.cog.callable_main(
1956 ["argv0", "-r", "-Dfooey=kablooey", "-Dbar=quux", "test.cog"]
1957 )
1958 self.assertFilesSame("test.cog", "test.kablooey")
1959 make_files(d)
1960 self.cog.callable_main(
1961 ["argv0", "-r", "-Dfooey=gooey", "-Dfooey=kablooey", "test.cog"]
1962 )
1963 self.assertFilesSame("test.cog", "test.kablooey")
1965 def test_output_to_stdout(self):
1966 d = {
1967 "test.cog": """\
1968 --[[[cog cog.outl('Hey there!') ]]]
1969 --[[[end]]]
1970 """
1971 }
1973 make_files(d)
1974 stderr = io.StringIO()
1975 self.cog.set_output(stderr=stderr)
1976 self.cog.callable_main(["argv0", "test.cog"])
1977 output = self.output.getvalue()
1978 outerr = stderr.getvalue()
1979 self.assertEqual(
1980 output, "--[[[cog cog.outl('Hey there!') ]]]\nHey there!\n--[[[end]]]\n"
1981 )
1982 self.assertEqual(outerr, "")
1984 def test_read_from_stdin(self):
1985 stdin = io.StringIO("--[[[cog cog.outl('Wow') ]]]\n--[[[end]]]\n")
1987 def restore_stdin(old_stdin):
1988 sys.stdin = old_stdin
1990 self.addCleanup(restore_stdin, sys.stdin)
1991 sys.stdin = stdin
1993 stderr = io.StringIO()
1994 self.cog.set_output(stderr=stderr)
1995 self.cog.callable_main(["argv0", "-"])
1996 output = self.output.getvalue()
1997 outerr = stderr.getvalue()
1998 self.assertEqual(output, "--[[[cog cog.outl('Wow') ]]]\nWow\n--[[[end]]]\n")
1999 self.assertEqual(outerr, "")
2001 def test_suffix_output_lines(self):
2002 d = {
2003 "test.cog": """\
2004 Hey there.
2005 ;[[[cog cog.outl('a\\nb\\n \\nc') ]]]
2006 ;[[[end]]]
2007 Good bye.
2008 """,
2009 "test.out": """\
2010 Hey there.
2011 ;[[[cog cog.outl('a\\nb\\n \\nc') ]]]
2012 a (foo)
2013 b (foo)
2014 """ # These three trailing spaces are important.
2015 # The suffix is not applied to completely blank lines.
2016 """
2017 c (foo)
2018 ;[[[end]]]
2019 Good bye.
2020 """,
2021 }
2023 make_files(d)
2024 self.cog.callable_main(["argv0", "-r", "-s", " (foo)", "test.cog"])
2025 self.assertFilesSame("test.cog", "test.out")
2027 def test_empty_suffix(self):
2028 d = {
2029 "test.cog": """\
2030 ;[[[cog cog.outl('a\\nb\\nc') ]]]
2031 ;[[[end]]]
2032 """,
2033 "test.out": """\
2034 ;[[[cog cog.outl('a\\nb\\nc') ]]]
2035 a
2036 b
2037 c
2038 ;[[[end]]]
2039 """,
2040 }
2042 make_files(d)
2043 self.cog.callable_main(["argv0", "-r", "-s", "", "test.cog"])
2044 self.assertFilesSame("test.cog", "test.out")
2046 def test_hellish_suffix(self):
2047 d = {
2048 "test.cog": """\
2049 ;[[[cog cog.outl('a\\n\\nb') ]]]
2050 """,
2051 "test.out": """\
2052 ;[[[cog cog.outl('a\\n\\nb') ]]]
2053 a /\\n*+([)]><
2055 b /\\n*+([)]><
2056 """,
2057 }
2059 make_files(d)
2060 self.cog.callable_main(["argv0", "-z", "-r", "-s", r" /\n*+([)]><", "test.cog"])
2061 self.assertFilesSame("test.cog", "test.out")
2063 def test_prologue(self):
2064 d = {
2065 "test.cog": """\
2066 Some text.
2067 //[[[cog cog.outl(str(math.sqrt(2))[:12])]]]
2068 //[[[end]]]
2069 epilogue.
2070 """,
2071 "test.out": """\
2072 Some text.
2073 //[[[cog cog.outl(str(math.sqrt(2))[:12])]]]
2074 1.4142135623
2075 //[[[end]]]
2076 epilogue.
2077 """,
2078 }
2080 make_files(d)
2081 self.cog.callable_main(["argv0", "-r", "-p", "import math", "test.cog"])
2082 self.assertFilesSame("test.cog", "test.out")
2084 def test_threads(self):
2085 # Test that the implicitly imported cog module is actually different for
2086 # different threads.
2087 numthreads = 20
2089 d = {}
2090 for i in range(numthreads):
2091 d[f"f{i}.cog"] = (
2092 "x\n" * i
2093 + "[[[cog\n"
2094 + f"assert cog.firstLineNum == int(FIRST) == {i + 1}\n"
2095 + "]]]\n"
2096 + "[[[end]]]\n"
2097 )
2098 make_files(d)
2100 results = []
2102 def thread_main(num):
2103 try:
2104 ret = Cog().main(
2105 ["cog.py", "-r", "-D", f"FIRST={num + 1}", f"f{num}.cog"]
2106 )
2107 assert ret == 0
2108 except Exception as exc: # pragma: no cover (only happens on test failure)
2109 results.append(exc)
2110 else:
2111 results.append(None)
2113 ts = [
2114 threading.Thread(target=thread_main, args=(i,)) for i in range(numthreads)
2115 ]
2116 for t in ts:
2117 t.start()
2118 for t in ts:
2119 t.join()
2120 assert results == [None] * numthreads
2123class CheckTests(TestCaseWithTempDir):
2124 def run_check(self, args, status=0):
2125 actual_status = self.cog.main(["argv0", "--check"] + args)
2126 print(self.output.getvalue())
2127 self.assertEqual(status, actual_status)
2129 def assert_made_files_unchanged(self, d):
2130 for name, content in d.items():
2131 content = reindent_block(content)
2132 if os.name == "nt":
2133 content = content.replace("\n", "\r\n")
2134 self.assertFileContent(name, content)
2136 def test_check_no_cog(self):
2137 d = {
2138 "hello.txt": """\
2139 Hello.
2140 """,
2141 }
2142 make_files(d)
2143 self.run_check(["hello.txt"], status=0)
2144 self.assertEqual(self.output.getvalue(), "Checking hello.txt\n")
2145 self.assert_made_files_unchanged(d)
2147 def test_check_good(self):
2148 d = {
2149 "unchanged.cog": """\
2150 //[[[cog
2151 cog.outl("hello world")
2152 //]]]
2153 hello world
2154 //[[[end]]]
2155 """,
2156 }
2157 make_files(d)
2158 self.run_check(["unchanged.cog"], status=0)
2159 self.assertEqual(self.output.getvalue(), "Checking unchanged.cog\n")
2160 self.assert_made_files_unchanged(d)
2162 def test_check_bad(self):
2163 d = {
2164 "changed.cog": """\
2165 //[[[cog
2166 cog.outl("goodbye world")
2167 //]]]
2168 hello world
2169 //[[[end]]]
2170 """,
2171 }
2172 make_files(d)
2173 self.run_check(["changed.cog"], status=5)
2174 self.assertEqual(
2175 self.output.getvalue(), "Checking changed.cog (changed)\nCheck failed\n"
2176 )
2177 self.assert_made_files_unchanged(d)
2179 def test_check_bad_with_diff(self):
2180 d = {
2181 "skittering.cog": """\
2182 //[[[cog
2183 for i in range(5): cog.outl(f"number {i}")
2184 cog.outl("goodbye world")
2185 //]]]
2186 number 0
2187 number 1
2188 number 2
2189 number 3
2190 number 4
2191 hello world
2192 //[[[end]]]
2193 """,
2194 }
2195 make_files(d)
2196 self.run_check(["--diff", "skittering.cog"], status=5)
2197 output = """\
2198 Checking skittering.cog (changed)
2199 --- current skittering.cog
2200 +++ changed skittering.cog
2201 @@ -7,5 +7,5 @@
2202 number 2
2203 number 3
2204 number 4
2205 -hello world
2206 +goodbye world
2207 //[[[end]]]
2208 Check failed
2209 """
2210 self.assertEqual(self.output.getvalue(), reindent_block(output))
2211 self.assert_made_files_unchanged(d)
2213 def test_check_mixed(self):
2214 d = {
2215 "unchanged.cog": """\
2216 //[[[cog
2217 cog.outl("hello world")
2218 //]]]
2219 hello world
2220 //[[[end]]]
2221 """,
2222 "changed.cog": """\
2223 //[[[cog
2224 cog.outl("goodbye world")
2225 //]]]
2226 hello world
2227 //[[[end]]]
2228 """,
2229 }
2230 make_files(d)
2231 for verbosity, output in [
2232 ("0", "Check failed\n"),
2233 ("1", "Checking changed.cog (changed)\nCheck failed\n"),
2234 (
2235 "2",
2236 "Checking unchanged.cog\nChecking changed.cog (changed)\nCheck failed\n",
2237 ),
2238 ]:
2239 self.new_cog()
2240 self.run_check(
2241 ["--verbosity=%s" % verbosity, "unchanged.cog", "changed.cog"], status=5
2242 )
2243 self.assertEqual(self.output.getvalue(), output)
2244 self.assert_made_files_unchanged(d)
2246 def test_check_with_good_checksum(self):
2247 d = {
2248 "good.txt": """\
2249 //[[[cog
2250 cog.outl("This line was newly")
2251 cog.outl("generated by cog")
2252 cog.outl("blah blah.")
2253 //]]]
2254 This line was newly
2255 generated by cog
2256 blah blah.
2257 //[[[end]]] (checksum: a8540982e5ad6b95c9e9a184b26f4346)
2258 """,
2259 }
2260 make_files(d)
2261 # Have to use -c with --check if there are checksums in the file.
2262 self.run_check(["-c", "good.txt"], status=0)
2263 self.assertEqual(self.output.getvalue(), "Checking good.txt\n")
2264 self.assert_made_files_unchanged(d)
2266 def test_check_with_bad_checksum(self):
2267 d = {
2268 "bad.txt": """\
2269 //[[[cog
2270 cog.outl("This line was newly")
2271 cog.outl("generated by cog")
2272 cog.outl("blah blah.")
2273 //]]]
2274 This line was newly
2275 generated by cog
2276 blah blah.
2277 //[[[end]]] (checksum: a9999999e5ad6b95c9e9a184b26f4346)
2278 """,
2279 }
2280 make_files(d)
2281 # Have to use -c with --check if there are checksums in the file.
2282 self.run_check(["-c", "bad.txt"], status=1)
2283 self.assertEqual(
2284 self.output.getvalue(),
2285 "Checking bad.txt\nbad.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
2286 )
2287 self.assert_made_files_unchanged(d)
2289 def test_check_with_good_sum(self):
2290 d = {
2291 "good.txt": """\
2292 //[[[cog
2293 cog.outl("This line was newly")
2294 cog.outl("generated by cog")
2295 cog.outl("blah blah.")
2296 //]]]
2297 This line was newly
2298 generated by cog
2299 blah blah.
2300 //[[[end]]] (sum: qFQJguWta5)
2301 """,
2302 }
2303 make_files(d)
2304 # Have to use -c with --check if there are checksums in the file.
2305 self.run_check(["-c", "good.txt"], status=0)
2306 self.assertEqual(self.output.getvalue(), "Checking good.txt\n")
2307 self.assert_made_files_unchanged(d)
2309 def test_check_with_bad_sum(self):
2310 d = {
2311 "bad.txt": """\
2312 //[[[cog
2313 cog.outl("This line was newly")
2314 cog.outl("generated by cog")
2315 cog.outl("blah blah.")
2316 //]]]
2317 This line was newly
2318 generated by cog
2319 blah blah.
2320 //[[[end]]] (sum: qZmZmeWta5)
2321 """,
2322 }
2323 make_files(d)
2324 # Have to use -c with --check if there are checksums in the file.
2325 self.run_check(["-c", "bad.txt"], status=1)
2326 self.assertEqual(
2327 self.output.getvalue(),
2328 "Checking bad.txt\nbad.txt(9): Output has been edited! Delete old checksum to unprotect.\n",
2329 )
2330 self.assert_made_files_unchanged(d)
2333class WritabilityTests(TestCaseWithTempDir):
2334 d = {
2335 "test.cog": """\
2336 //[[[cog
2337 for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']:
2338 cog.outl("void %s();" % fn)
2339 //]]]
2340 //[[[end]]]
2341 """,
2342 "test.out": """\
2343 //[[[cog
2344 for fn in ['DoSomething', 'DoAnotherThing', 'DoLastThing']:
2345 cog.outl("void %s();" % fn)
2346 //]]]
2347 void DoSomething();
2348 void DoAnotherThing();
2349 void DoLastThing();
2350 //[[[end]]]
2351 """,
2352 }
2354 if os.name == "nt": 2354 ↛ 2356line 2354 didn't jump to line 2356 because the condition on line 2354 was never true
2355 # for Windows
2356 cmd_w_args = "attrib -R %s"
2357 cmd_w_asterisk = "attrib -R *"
2358 else:
2359 # for unix-like
2360 cmd_w_args = "chmod +w %s"
2361 cmd_w_asterisk = "chmod +w *"
2363 def setUp(self):
2364 super().setUp()
2365 make_files(self.d)
2366 self.testcog = os.path.join(self.tempdir, "test.cog")
2367 os.chmod(self.testcog, stat.S_IREAD) # Make the file readonly.
2368 assert not os.access(self.testcog, os.W_OK)
2370 def tearDown(self):
2371 os.chmod(self.testcog, stat.S_IWRITE) # Make the file writable again.
2372 super().tearDown()
2374 def test_readonly_no_command(self):
2375 with self.assertRaisesRegex(CogError, "^Can't overwrite test.cog$"):
2376 self.cog.callable_main(["argv0", "-r", "test.cog"])
2377 assert not os.access(self.testcog, os.W_OK)
2379 def test_readonly_with_command(self):
2380 self.cog.callable_main(["argv0", "-r", "-w", self.cmd_w_args, "test.cog"])
2381 self.assertFilesSame("test.cog", "test.out")
2382 assert os.access(self.testcog, os.W_OK)
2384 def test_readonly_with_command_with_no_slot(self):
2385 self.cog.callable_main(["argv0", "-r", "-w", self.cmd_w_asterisk, "test.cog"])
2386 self.assertFilesSame("test.cog", "test.out")
2387 assert os.access(self.testcog, os.W_OK)
2389 def test_readonly_with_ineffectual_command(self):
2390 with self.assertRaisesRegex(CogError, "^Couldn't make test.cog writable$"):
2391 self.cog.callable_main(["argv0", "-r", "-w", "echo %s", "test.cog"])
2392 assert not os.access(self.testcog, os.W_OK)
2395class ChecksumTests(TestCaseWithTempDir):
2396 def test_create_checksum_output(self):
2397 d = {
2398 "cog1.txt": """\
2399 //[[[cog
2400 cog.outl("This line was generated.")
2401 //]]]
2402 This line was generated.
2403 //[[[end]]] what
2404 This line was not.
2405 """,
2406 "cog1.out": """\
2407 //[[[cog
2408 cog.outl("This line was generated.")
2409 //]]]
2410 This line was generated.
2411 //[[[end]]] (sum: itsT+1m5lq) what
2412 This line was not.
2413 """,
2414 }
2416 make_files(d)
2417 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2418 self.assertFilesSame("cog1.txt", "cog1.out")
2420 def test_check_checksum_output(self):
2421 d = {
2422 "cog1.txt": """\
2423 //[[[cog
2424 cog.outl("This line was newly")
2425 cog.outl("generated by cog")
2426 cog.outl("blah blah.")
2427 //]]]
2428 This line was generated.
2429 //[[[end]]] (sum: itsT+1m5lq) end
2430 """,
2431 "cog1.out": """\
2432 //[[[cog
2433 cog.outl("This line was newly")
2434 cog.outl("generated by cog")
2435 cog.outl("blah blah.")
2436 //]]]
2437 This line was newly
2438 generated by cog
2439 blah blah.
2440 //[[[end]]] (sum: qFQJguWta5) end
2441 """,
2442 }
2444 make_files(d)
2445 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2446 self.assertFilesSame("cog1.txt", "cog1.out")
2448 def test_check_old_checksum_format(self):
2449 # Test that old checksum format can still be read
2450 d = {
2451 "cog1.txt": """\
2452 //[[[cog
2453 cog.outl("This line was newly")
2454 cog.outl("generated by cog")
2455 cog.outl("blah blah.")
2456 //]]]
2457 This line was generated.
2458 //[[[end]]] (checksum: 8adb13fb59b996a1c7f0065ea9f3d893) end
2459 """,
2460 "cog1.out": """\
2461 //[[[cog
2462 cog.outl("This line was newly")
2463 cog.outl("generated by cog")
2464 cog.outl("blah blah.")
2465 //]]]
2466 This line was newly
2467 generated by cog
2468 blah blah.
2469 //[[[end]]] (sum: qFQJguWta5) end
2470 """,
2471 }
2473 make_files(d)
2474 self.cog.callable_main(["argv0", "-r", "-c", "cog1.txt"])
2475 self.assertFilesSame("cog1.txt", "cog1.out")
2477 def test_remove_checksum_output(self):
2478 d = {
2479 "cog1.txt": """\
2480 //[[[cog
2481 cog.outl("This line was newly")
2482 cog.outl("generated by cog")
2483 cog.outl("blah blah.")
2484 //]]]
2485 This line was generated.
2486 //[[[end]]] (sum: itsT+1m5lq) fooey
2487 """,
2488 "cog1.out": """\
2489 //[[[cog
2490 cog.outl("This line was newly")
2491 cog.outl("generated by cog")
2492 cog.outl("blah blah.")
2493 //]]]
2494 This line was newly
2495 generated by cog
2496 blah blah.
2497 //[[[end]]] fooey
2498 """,
2499 }
2501 make_files(d)
2502 self.cog.callable_main(["argv0", "-r", "cog1.txt"])
2503 self.assertFilesSame("cog1.txt", "cog1.out")
2505 def test_tampered_checksum_output(self):
2506 d = {
2507 "cog1.txt": """\
2508 //[[[cog
2509 cog.outl("This line was newly")
2510 cog.outl("generated by cog")
2511 cog.outl("blah blah.")
2512 //]]]
2513 Xhis line was newly
2514 generated by cog
2515 blah blah.
2516 //[[[end]]] (sum: qFQJguWta5)
2517 """,
2518 "cog2.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 newly
2525 generated by cog
2526 blah blah!
2527 //[[[end]]] (sum: qFQJguWta5)
2528 """,
2529 "cog3.txt": """\
2530 //[[[cog
2531 cog.outl("This line was newly")
2532 cog.outl("generated by cog")
2533 cog.outl("blah blah.")
2534 //]]]
2536 This line was newly
2537 generated by cog
2538 blah blah.
2539 //[[[end]]] (sum: qFQJguWta5)
2540 """,
2541 "cog4.txt": """\
2542 //[[[cog
2543 cog.outl("This line was newly")
2544 cog.outl("generated by cog")
2545 cog.outl("blah blah.")
2546 //]]]
2547 This line was newly
2548 generated by cog
2549 blah blah..
2550 //[[[end]]] (sum: qFQJguWta5)
2551 """,
2552 "cog5.txt": """\
2553 //[[[cog
2554 cog.outl("This line was newly")
2555 cog.outl("generated by cog")
2556 cog.outl("blah blah.")
2557 //]]]
2558 This line was newly
2559 generated by cog
2560 blah blah.
2561 extra
2562 //[[[end]]] (sum: qFQJguWta5)
2563 """,
2564 "cog6.txt": """\
2565 //[[[cog
2566 cog.outl("This line was newly")
2567 cog.outl("generated by cog")
2568 cog.outl("blah blah.")
2569 //]]]
2570 //[[[end]]] (sum: qFQJguWta5)
2571 """,
2572 }
2574 make_files(d)
2575 with self.assertRaisesRegex(
2576 CogError,
2577 r"^cog1.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2578 ):
2579 self.cog.callable_main(["argv0", "-c", "cog1.txt"])
2580 with self.assertRaisesRegex(
2581 CogError,
2582 r"^cog2.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2583 ):
2584 self.cog.callable_main(["argv0", "-c", "cog2.txt"])
2585 with self.assertRaisesRegex(
2586 CogError,
2587 r"^cog3.txt\(10\): Output has been edited! Delete old checksum to unprotect.$",
2588 ):
2589 self.cog.callable_main(["argv0", "-c", "cog3.txt"])
2590 with self.assertRaisesRegex(
2591 CogError,
2592 r"^cog4.txt\(9\): Output has been edited! Delete old checksum to unprotect.$",
2593 ):
2594 self.cog.callable_main(["argv0", "-c", "cog4.txt"])
2595 with self.assertRaisesRegex(
2596 CogError,
2597 r"^cog5.txt\(10\): Output has been edited! Delete old checksum to unprotect.$",
2598 ):
2599 self.cog.callable_main(["argv0", "-c", "cog5.txt"])
2600 with self.assertRaisesRegex(
2601 CogError,
2602 r"^cog6.txt\(6\): Output has been edited! Delete old checksum to unprotect.$",
2603 ):
2604 self.cog.callable_main(["argv0", "-c", "cog6.txt"])
2606 def test_argv_isnt_modified(self):
2607 argv = ["argv0", "-v"]
2608 orig_argv = argv[:]
2609 self.cog.callable_main(argv)
2610 self.assertEqual(argv, orig_argv)
2613class CustomMarkerTests(TestCaseWithTempDir):
2614 def test_customer_markers(self):
2615 d = {
2616 "test.cog": """\
2617 //{{
2618 cog.outl("void %s();" % "MyFunction")
2619 //}}
2620 //{{end}}
2621 """,
2622 "test.out": """\
2623 //{{
2624 cog.outl("void %s();" % "MyFunction")
2625 //}}
2626 void MyFunction();
2627 //{{end}}
2628 """,
2629 }
2631 make_files(d)
2632 self.cog.callable_main(["argv0", "-r", "--markers={{ }} {{end}}", "test.cog"])
2633 self.assertFilesSame("test.cog", "test.out")
2635 def test_truly_wacky_markers(self):
2636 # Make sure the markers are properly re-escaped.
2637 d = {
2638 "test.cog": """\
2639 //**(
2640 cog.outl("void %s();" % "MyFunction")
2641 //**)
2642 //**(end)**
2643 """,
2644 "test.out": """\
2645 //**(
2646 cog.outl("void %s();" % "MyFunction")
2647 //**)
2648 void MyFunction();
2649 //**(end)**
2650 """,
2651 }
2653 make_files(d)
2654 self.cog.callable_main(
2655 ["argv0", "-r", "--markers=**( **) **(end)**", "test.cog"]
2656 )
2657 self.assertFilesSame("test.cog", "test.out")
2659 def test_change_just_one_marker(self):
2660 d = {
2661 "test.cog": """\
2662 //**(
2663 cog.outl("void %s();" % "MyFunction")
2664 //]]]
2665 //[[[end]]]
2666 """,
2667 "test.out": """\
2668 //**(
2669 cog.outl("void %s();" % "MyFunction")
2670 //]]]
2671 void MyFunction();
2672 //[[[end]]]
2673 """,
2674 }
2676 make_files(d)
2677 self.cog.callable_main(
2678 ["argv0", "-r", "--markers=**( ]]] [[[end]]]", "test.cog"]
2679 )
2680 self.assertFilesSame("test.cog", "test.out")
2683class BlakeTests(TestCaseWithTempDir):
2684 # Blake Winton's contributions.
2685 def test_delete_code(self):
2686 # -o sets the output file.
2687 d = {
2688 "test.cog": """\
2689 // This is my C++ file.
2690 //[[[cog
2691 fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
2692 for fn in fnames:
2693 cog.outl("void %s();" % fn)
2694 //]]]
2695 Some Sample Code Here
2696 //[[[end]]]Data Data
2697 And Some More
2698 """,
2699 "test.out": """\
2700 // This is my C++ file.
2701 void DoSomething();
2702 void DoAnotherThing();
2703 void DoLastThing();
2704 And Some More
2705 """,
2706 }
2708 make_files(d)
2709 self.cog.callable_main(["argv0", "-d", "-o", "test.cogged", "test.cog"])
2710 self.assertFilesSame("test.cogged", "test.out")
2712 def test_delete_code_with_dash_r_fails(self):
2713 d = {
2714 "test.cog": """\
2715 // This is my C++ file.
2716 """
2717 }
2719 make_files(d)
2720 with self.assertRaisesRegex(
2721 CogUsageError,
2722 r"^Can't use -d with -r \(or you would delete all your source!\)$",
2723 ):
2724 self.cog.callable_main(["argv0", "-r", "-d", "test.cog"])
2726 def test_setting_globals(self):
2727 # Blake Winton contributed a way to set the globals that will be used in
2728 # processFile().
2729 d = {
2730 "test.cog": """\
2731 // This is my C++ file.
2732 //[[[cog
2733 for fn in fnames:
2734 cog.outl("void %s();" % fn)
2735 //]]]
2736 Some Sample Code Here
2737 //[[[end]]]""",
2738 "test.out": """\
2739 // This is my C++ file.
2740 void DoBlake();
2741 void DoWinton();
2742 void DoContribution();
2743 """,
2744 }
2746 make_files(d)
2747 globals = {}
2748 globals["fnames"] = ["DoBlake", "DoWinton", "DoContribution"]
2749 self.cog.options.delete_code = True
2750 self.cog.process_file("test.cog", "test.cogged", globals=globals)
2751 self.assertFilesSame("test.cogged", "test.out")
2754class ErrorCallTests(TestCaseWithTempDir):
2755 def test_error_call_has_no_traceback(self):
2756 # Test that cog.error() doesn't show a traceback.
2757 d = {
2758 "error.cog": """\
2759 //[[[cog
2760 cog.error("Something Bad!")
2761 //]]]
2762 //[[[end]]]
2763 """,
2764 }
2766 make_files(d)
2767 self.cog.main(["argv0", "-r", "error.cog"])
2768 output = self.output.getvalue()
2769 self.assertEqual(output, "Cogging error.cog\nError: Something Bad!\n")
2771 def test_real_error_has_traceback(self):
2772 # Test that a genuine error does show a traceback.
2773 d = {
2774 "error.cog": """\
2775 //[[[cog
2776 raise RuntimeError("Hey!")
2777 //]]]
2778 //[[[end]]]
2779 """,
2780 }
2782 make_files(d)
2783 self.cog.main(["argv0", "-r", "error.cog"])
2784 output = self.output.getvalue()
2785 msg = "Actual output:\n" + output
2786 self.assertTrue(
2787 output.startswith("Cogging error.cog\nTraceback (most recent"), msg
2788 )
2789 self.assertIn("RuntimeError: Hey!", output)
2792class HashHandlerTests(TestCase):
2793 """Test cases for HashHandler functionality."""
2795 def setUp(self):
2796 self.handler = HashHandler("[[[end]]]")
2798 def test_validate_hash_with_base64_mismatch(self):
2799 # Test the base64 validation branch with a mismatch
2800 line = "//[[[end]]] (sum: wronghas12)" # 10 chars to match regex
2801 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346"
2803 with self.assertRaises(ValueError) as cm:
2804 self.handler.validate_hash(line, expected_hash)
2805 self.assertEqual(
2806 str(cm.exception),
2807 "Output has been edited! Delete old checksum to unprotect.",
2808 )
2810 def test_validate_hash_with_base64_match(self):
2811 # Test the base64 validation branch with a match
2812 line = "//[[[end]]] (sum: qFQJguWta5)"
2813 expected_hash = "a8540982e5ad6b95c9e9a184b26f4346"
2815 # Should not raise an exception
2816 result = self.handler.validate_hash(line, expected_hash)
2817 self.assertTrue(result)
2820# Things not yet tested:
2821# - A bad -w command (currently fails silently).