2 # -*- coding: iso-8859-1 -*-
4 ## Test the pylit.py literal python module
5 ## =======================================
9 ## :Copyright: 2006 Guenter Milde.
10 ## Released under the terms of the GNU General Public License
17 """pylit_test.py: test the "literal python" module"""
19 from pprint
import pprint
22 ## Text <-> Code conversion
23 ## ========================
28 ## Example of text, code and stripped code with typical features"::
30 text
= """.. #!/usr/bin/env python
31 # -*- coding: iso-8859-1 -*-
35 in several paragraphs followed by a literal block::
37 block1 = 'first block'
39 Some more text and the next block. ::
41 block2 = 'second block'
48 ## The converter expects the data in separate lines (iterator or list)
49 ## with trailing newlines. We use the `splitlines` string method with
52 textdata
= text
.splitlines(True)
55 ## If a "code source" is converted with the `strip` option, only text blocks
56 ## are extracted, which leads to::
58 stripped_text
= """Leading text
60 in several paragraphs followed by a literal block:
62 Some more text and the next block.
67 ## The code corresponding to the text test string.
69 ## Using a triple-quoted string for the code (and stripped_code) can create
70 ## problems with the conversion of this test by pylit (as the text parts
71 ## would be converted to text). This is catered for by using a different
72 ## comment string for the text blocks in this file: convert to text with
73 ## ``pylit --comment-string='## ' pylit_test.py``::
75 code
= """#!/usr/bin/env python
76 # -*- coding: iso-8859-1 -*-
80 # in several paragraphs followed by a literal block::
82 block1 = 'first block'
84 # Some more text and the next block. ::
86 block2 = 'second block'
93 codedata
= code
.splitlines(True)
95 ## Converting the text teststring with the `strip` option leads to::
97 stripped_code
= """#!/usr/bin/env python
98 # -*- coding: iso-8859-1 -*-
100 block1 = 'first block'
102 block2 = 'second block'
108 ## pprint(stripped_code.splitlines(True))
110 ## Containers for special case examples:
112 ## 1. Text2Code samples
113 ## ``textsamples["what"] = (<text data>, <output>, <output (with `strip`)``
118 ## 2. Code2Txt samples
119 ## ``codesamples["what"] = (<code data>, <output>, <output (with `strip`)``
124 ## Auxiliary function to test the textsamples and codesamples::
126 def check_converter(key
, converter
, output
):
128 extract
= converter()
130 outstr
= "".join(["".join(block
) for block
in extract
])
131 print "ist: ", repr(outstr
)
132 print "soll:", repr(output
)
133 assert output
== outstr
135 ## Test generator for textsample tests::
137 def test_Text2Code_samples():
138 for key
, sample
in textsamples
.iteritems():
139 yield (check_converter
, key
,
140 Text2Code(sample
[0].splitlines(True)), sample
[1])
142 yield (check_converter
, key
,
143 Text2Code(sample
[0].splitlines(True), strip
=True),
146 ## Test generator for codesample tests::
148 def test_Code2Text_samples():
149 for key
, sample
in codesamples
.iteritems():
150 yield (check_converter
, key
,
151 Code2Text(sample
[0].splitlines(True)), sample
[1])
153 yield (check_converter
, key
,
154 Code2Text(sample
[0].splitlines(True), strip
=True),
160 ## base tests on the "long" test data ::
162 def test_Text2Code():
163 """Test the Text2Code class converting rst->code"""
164 outstr
= str(Text2Code(textdata
))
167 assert code
== outstr
169 def test_Text2Code_strip():
170 """strip=True should strip text parts"""
171 outstr
= str(Text2Code(textdata
, strip
=True))
172 print "soll", repr(stripped_code
)
173 print "ist ", repr(outstr
)
175 assert stripped_code
== outstr
177 def test_Text2Code_malindented_code_line():
178 """raise error if code line is less indented than code-indent"""
179 data1
= [".. #!/usr/bin/env python\n", # indent == 4 * " "
181 " print 'hello world'"] # indent == 2 * " "
182 data2
= ["..\t#!/usr/bin/env python\n", # indent == 4 * " "
184 " print 'hello world'"] # indent == 2 * " "
185 for data
in (data1
, data2
):
187 blocks
= Text2Code(data
)()
188 assert False, "wrong indent did not raise ValueError"
195 ## Code follows text block without blank line
196 ## ''''''''''''''''''''''''''''''''''''''''''
198 ## End of text block detected ('::') but no paragraph separator (blank line)
201 ## It is an reStructuredText syntax error, if a "literal block
202 ## marker" is not followed by a blank line.
204 ## Assuming that no double colon at end of line occures accidentially,
205 ## pylit will fix this and issue a warning::
207 textsamples
["ensure blank line after text"] = (
208 """text followed by a literal block::
209 block1 = 'first block'
211 """# text followed by a literal block::
213 block1 = 'first block'
216 ## Text follows code block without blank line
217 ## ''''''''''''''''''''''''''''''''''''''''''
219 ## End of code block detected (a line not more indented than the preceding text
222 ## reStructuredText syntax demands a paragraph separator (blank line) before
225 ## Assuming that the unindent is not accidential, pylit fixes this and issues a
228 textsamples
["ensure blank line after code"] = (
231 block1 = 'first block'
236 block1 = 'first block'
241 ## A double colon on a line on its own
242 ## '''''''''''''''''''''''''''''''''''
244 ## As a double colon is added by the Code2Text conversion after a text block
245 ## (if not already present), it could be removed by the Text2Code conversion
246 ## to keep the source small and pretty.
248 ## However, this would put the text and code source line numbers out of sync,
249 ## which is bad for error reporting, failing doctests, and the `pylit_buffer()`
250 ## function in http://jedmodes.sf.net/mode/pylit.sl ::
252 ## textsamples["should remove single double colon"] = (
253 ## ["text followed by a literal block\n",
257 ## " foo = 'first'\n"]
258 ## ["", # empty header
259 ## "# text followed by a literal block\n\n",
260 ## "foo = 'first'\n"]
265 ## Convert a leading reStructured text comment (variant: only if there is
266 ## content on the first line) to a leading code block. Return an empty list,
267 ## if there is no header. ::
269 textsamples
["simple header"] = (".. print 'hello world'",
270 "print 'hello world'")
272 textsamples
["no header (start with text)"] = (
273 """a classical example without header::
277 """# a classical example without header::
282 textsamples
["standard header, followed by text"] = (
283 """.. #!/usr/bin/env python
284 # -*- coding: iso-8859-1 -*-
286 a classical example with header::
290 """#!/usr/bin/env python
291 # -*- coding: iso-8859-1 -*-
293 # a classical example with header::
298 textsamples
["standard header, followed by code"] = (
299 """.. #!/usr/bin/env python
303 """#!/usr/bin/env python
311 ## Code2Text.strip_literal_marker
313 ## * strip `::`-line as well as preceding blank line if on a line on its own
314 ## * strip `::` if it is preceded by whitespace.
315 ## * convert `::` to a single colon if preceded by text
317 def test_Code2Text_strip_literal_marker():
318 c2t_instance
= Code2Text(codedata
)
319 samples
= ((["text\n", "\n", "::\n", "\n"], ["text\n", "\n"]),
320 (["text ::\n", "\n"], ["text\n", "\n"]),
321 (["text::\n", "\n"], ["text:\n", "\n"]))
322 for (ist
, soll
) in samples
:
323 c2t_instance
.strip_literal_marker(ist
)
327 ## base tests on the "long" test strings ::
329 def test_Code2Text():
330 """Test Code2Text class converting code->text"""
331 outstr
= str(Code2Text(codedata
))
335 assert text
== outstr
337 def test_Code2Text_strip():
338 """Test Code2Text class converting code->rst with strip=True
340 Should strip code blocks
342 pprint(Code2Text(codedata
, strip
=True)())
343 outstr
= str(Code2Text(codedata
, strip
=True))
344 print repr(stripped_text
)
346 assert stripped_text
== outstr
348 def test_Code2Text_different_comment_string():
349 """Convert only comments with the specified comment string to text
351 outstr
= str(Code2Text(codedata
, comment_string
="##", strip
=True))
356 "block1 = 'first block'\n",
362 " block1 = 'first block'\n",
363 '\n'], # leading code block as header
364 [' more text'] # keep space (not part of comment string)
366 output
= Code2Text(data
, comment_string
="##")()
369 assert output
== soll
374 ## blank comment line
375 ## ''''''''''''''''''''
377 ## Normally, whitespace in the comment string is significant, i.e. with
378 ## `comment_string = "# "`, a line "#something\n" will count as code.
380 ## However, if a comment line is blank, trailing whitespace in the comment
381 ## string should be ignored, i.e. "#\n" is recognized as a blank text line::
383 codesamples
["ignore trailing whitespace in comment string for blank line"] = (
386 block1 = 'first block'
393 block1 = 'first block'
399 ## No blank line after text
400 ## ''''''''''''''''''''''''
402 ## If a matching comment precedes oder follows a code line (i.e. any line
403 ## without matching comment) without a blank line inbetween, it counts as code
406 ## This will keep small inline comments close to the code they comment on. It
407 ## will also keep blocks together where one commented line doesnot match the
408 ## comment string (the whole block will be kept as commented code)
411 codesamples
["comment before code (without blank line)"] = (
426 codesamples
["comment block before code (without blank line)"] = (
427 """# no text (watch the comment sign in the next line)::
432 """.. # no text (watch the comment sign in the next line)::
439 codesamples
["comment after code (without blank line)"] = (
442 block1 = 'first block'
449 block1 = 'first block'
458 codesamples
["comment block after code (without blank line)"] = (
461 block1 = 'first block'
468 block1 = 'first block'
476 ## missing literal block marker
477 ## ''''''''''''''''''''''''''''
479 ## If text (with matching comment string) is followed by code (line(s) without
480 ## matching comment string), but there is no double colon at the end, back
481 ## conversion would not recognize the end of text!
483 ## Therefore, pylit adds a paragraph containing only "::" -- the literal block
484 ## marker in expanded form. (While it would in many cases be nicer to add the
485 ## double colon to the last text line, this is not always valid rst syntax,
486 ## e.g. after a section header or a list. Therefore the automatic insertion
487 ## will use the save form, feel free to correct this by hand.)::
489 codesamples
["insert missing double colon after text block"] = (
490 """# text followed by code without double colon
494 """text followed by code without double colon
500 """text followed by code without double colon
507 ## Convert a header (leading code block) to a reStructured text comment. ::
509 codesamples
["no matching comment, just code"] = ("print 'hello world'",
510 ".. print 'hello world'")
512 codesamples
["empty header (start with matching comment)"] = (
513 """# a classical example without header::
517 """a classical example without header::
521 """a classical example without header:
525 codesamples
["standard header, followed by text"] = (
526 """#!/usr/bin/env python
527 # -*- coding: iso-8859-1 -*-
529 # a classical example with header::
533 """.. #!/usr/bin/env python
534 # -*- coding: iso-8859-1 -*-
536 a classical example with header::
540 """a classical example with header:
544 codesamples
["standard header, followed by code"] = (
545 """#!/usr/bin/env python
549 """.. #!/usr/bin/env python
558 ## Test the option parsing::
561 values
= OptionValues()
563 defaults
= {"a1": 1, "a2": False}
564 values
= OptionValues(defaults
)
565 print values
, values
.as_dict()
566 assert values
.a1
== 1
567 assert values
.a2
== False
568 assert values
.as_dict() == defaults
570 class test_PylitOptions
:
571 """Test the PylitOption class"""
573 self
.options
= PylitOptions()
575 def test_languages_and_extensions(self
):
576 """dictionary of programming languages and extensions"""
577 for ext
in [".py", ".sl", ".c"]:
578 assert ext
in self
.options
.code_extensions
579 assert self
.options
.code_languages
[".py"] == "python"
580 assert self
.options
.code_languages
[".sl"] == "slang"
581 assert self
.options
.code_languages
[".c"] == "c++"
583 def test_parse_args(self
):
584 """parse cmd line args"""
585 # default should appear in options
586 values
= self
.options
.parse_args(txt2code
=False)
587 print values
, type(values
), dir(values
)
588 assert values
.txt2code
== False
589 # "cmd line arg should appear as option overwriting default"
590 values
= self
.options
.parse_args(["--txt2code"], txt2code
=False)
591 assert values
.txt2code
== True
592 # "1st non option arg is infile, 2nd is outfile"
593 values
= self
.options
.parse_args(["--txt2code", "text.txt", "code.py"])
595 assert values
.infile
== "text.txt"
596 assert values
.outfile
== "code.py"
597 # set the output (option with argument)
598 values
= self
.options
.parse_args(["--outfile", "code.py"])
599 assert values
.outfile
== "code.py"
601 def test_parse_args_comment_string(self
):
602 # default should appear in options
603 values
= self
.options
.parse_args(["--comment-string=% "])
604 pprint(values
.as_dict())
605 assert values
.comment_string
== "% "
606 # "cmd line arg should appear as option overwriting default"
607 values
= self
.options
.parse_args(["--comment-string=% "],
609 assert values
.comment_string
== '% '
611 def test_get_outfile_name(self
):
612 """should return a sensible outfile name given an infile name"""
613 # return stdout for stdin
614 assert "-" == self
.options
.get_outfile_name("-")
615 # return with ".txt" stripped
616 assert "foo.py" == self
.options
.get_outfile_name("foo.py.txt")
617 # return with ".txt" added if extension marks code file
618 assert "foo.py.txt" == self
.options
.get_outfile_name("foo.py")
619 assert "foo.sl.txt" == self
.options
.get_outfile_name("foo.sl")
620 assert "foo.c.txt" == self
.options
.get_outfile_name("foo.c")
621 # return with ".txt" added if txt2code == False (not None!)
622 assert "foo.py.txt" == self
.options
.get_outfile_name("foo.py", txt2code
=False)
623 # catchall: add ".out" if no other guess possible
624 assert "foo.out" == self
.options
.get_outfile_name("foo", txt2code
=None)
626 def test_complete_values(self
):
627 """Basic test of the option completion"""
628 values
= optparse
.Values()
629 values
.infile
= "foo"
630 values
= self
.options
.complete_values(values
)
631 # the following options should be set:
632 print values
.infile
# logo, as we give it...
634 assert values
.outfile
== "foo.out" # fallback extension .out added
635 print values
.txt2code
636 assert values
.txt2code
== True # the default
637 print values
.language
638 assert values
.language
== "python" # the default
640 def test_complete_values_txt(self
):
641 """Test the option completion with a text input file"""
642 values
= optparse
.Values()
643 values
.infile
= "foo.txt"
644 values
= self
.options
.complete_values(values
)
645 # should set outfile (see also `test_get_outfile_name`)
646 assert values
.outfile
== "foo"
647 # should set conversion direction according to extension
648 assert values
.txt2code
== True
650 def test_complete_values_code(self
):
651 """Test the option completion with a code input file"""
652 values
= optparse
.Values()
653 values
.infile
= "foo.py"
654 values
= self
.options
.complete_values(values
)
655 # should set outfile name
656 assert values
.outfile
== "foo.py.txt"
657 # should set conversion directions according to extension
658 print values
.txt2code
659 assert values
.txt2code
== False
661 def test_complete_values_dont_overwrite(self
):
662 """The option completion must not overwrite existing option values"""
663 values
= optparse
.Values()
664 values
.infile
= "foo.py"
665 values
.outfile
= "bar.txt"
666 values
.txt2code
= True
667 values
= self
.options
.complete_values(values
)
668 assert values
.outfile
== "bar.txt"
669 assert values
.txt2code
== True
672 options
= PylitOptions(["--txt2code", "foo"], txt2code
=False)
674 assert options
.values
.txt2code
== True
675 assert options
.values
.infile
== "foo"
677 ## Input and Output streams
678 ## ------------------------
683 """base class for IO tests, sets up and tears down example files in /tmp
685 txtpath
= "/tmp/pylit_test.py.txt"
686 codepath
= "/tmp/pylit_test.py"
687 outpath
= "/tmp/pylit_test.out"
690 """Set up the test files"""
691 txtfile
= file(self
.txtpath
, 'w')
693 # txtfile.flush() # is this needed if we close?
696 codefile
= file(self
.codepath
, 'w')
698 # codefile.flush() # is this needed if we close?
702 """clean up after all member tests are done"""
704 os
.unlink(self
.txtpath
)
705 os
.unlink(self
.codepath
)
706 os
.unlink(self
.outpath
)
710 class test_Streams(IOTests
):
711 def test_is_newer(self
):
712 # this __file__ is older, than code file
713 print __file__
, os
.path
.getmtime(__file__
)
714 print self
.codepath
, os
.path
.getmtime(self
.codepath
)
716 assert is_newer(self
.codepath
, __file__
) is True, "file1 is newer"
717 assert is_newer(__file__
, self
.codepath
) is False, "file2 is newer"
718 assert is_newer(__file__
, "fffo") is True, "file2 doesnot exist"
719 assert is_newer("fflo", __file__
) is False, "file1 doesnot exist"
721 assert is_newer(__file__
, __file__
) is None, "equal is not newer"
722 assert is_newer("fflo", "fffo") is None, "no file exists -> equal"
724 def test_open_streams(self
):
725 # default should return stdin and -out:
726 (instream
, outstream
) = open_streams()
727 assert instream
is sys
.stdin
728 assert outstream
is sys
.stdout
730 # open input and output file
731 (instream
, outstream
) = open_streams(self
.txtpath
, self
.outpath
)
732 assert type(instream
) == file
733 assert type(outstream
) == file
734 # read something from the input
735 assert instream
.read() == text
736 # write something to the output
737 outstream
.write(text
)
738 # check the output, we have to flush first
740 outfile
= file(self
.outpath
, 'r')
741 assert outfile
.read() == text
743 def test_open_streams_no_infile(self
):
744 """should exit with usage info if no infile given"""
746 (instream
, outstream
) = open_streams("")
747 assert False, "should rise SystemExit"
751 ## Another convenience function that returns a converter instance::
753 def test_get_converter():
754 # with default or txt2code
755 converter
= get_converter(textdata
)
756 print converter
.__class
__
757 assert converter
.__class
__ == Text2Code
758 converter
= get_converter(textdata
, txt2code
=False)
759 assert converter
.__class
__ == Code2Text
761 # the run_doctest runs a doctest on the text version (as doc-string)
762 class test_Run_Doctest(IOTests
):
763 """Doctest should run on the text source"""
764 def test_doctest_txt2code(self
):
765 (failures
, tests
) = run_doctest(self
.txtpath
, txt2code
=True)
766 assert (failures
, tests
) == (0, 0)
767 def test_doctest_code2txt(self
):
768 (failures
, tests
) = run_doctest(self
.codepath
, txt2code
=False)
769 assert (failures
, tests
) == (0, 0)
771 ## The main() function is called if the script is run from the command line
775 class test_Main(IOTests
):
776 """test default operation from command line
778 def get_output(self
):
779 """read and return the content of the output file"""
780 outstream
= file(self
.outpath
, 'r')
781 return outstream
.read()
783 def test_text_to_code(self
):
784 """test conversion of text file to code file"""
785 main(infile
=self
.txtpath
, outfile
=self
.outpath
)
786 output
= self
.get_output()
788 assert output
== code
790 def test_text_to_code_strip(self
):
791 """test conversion of text file to stripped code file"""
792 main(infile
=self
.txtpath
, outfile
=self
.outpath
, strip
=True)
793 output
= self
.get_output()
795 assert output
== stripped_code
797 def test_main_code_to_text(self
):
798 """test conversion of code file to text file"""
799 main(infile
=self
.codepath
, outfile
=self
.outpath
)
800 output
= self
.get_output()
801 assert output
== text
803 def test_main_code_to_text_strip(self
):
804 """test conversion of code file to stripped text file"""
805 main(infile
=self
.codepath
, outfile
=self
.outpath
, strip
=True)
806 output
= self
.get_output()
807 assert output
== stripped_text
809 def test_main_diff(self
):
810 result
= main(infile
=self
.codepath
, diff
=True)
811 print "diff return value", result
812 assert result
is False # no differences found
814 def test_main_diff_with_differences(self
):
815 """diffing a file to itself should fail, as the input is converted"""
816 result
= main(infile
=self
.codepath
, outfile
=self
.codepath
, diff
=True)
817 print "diff return value", result
818 assert result
is True # differences found
820 def test_main_execute(self
):
821 result
= main(infile
=self
.txtpath
, execute
=True)
824 def test_main_execute_code(self
):
825 result
= main(infile
=self
.codepath
, execute
=True)
828 nose
.runmodule() # requires nose 0.9.1