Merge lp:~adiroiban/pocket-lint/986239 into lp:pocket-lint

Proposed by Adi Roiban
Status: Merged
Approved by: Curtis Hovey
Approved revision: 469
Merged at revision: 468
Proposed branch: lp:~adiroiban/pocket-lint/986239
Merge into: lp:pocket-lint
Diff against target: 868 lines (+790/-2)
4 files modified
pocketlint/formatcheck.py (+245/-0)
pocketlint/tests/__init__.py (+1/-0)
pocketlint/tests/test_restructuredtext.py (+524/-0)
test.py (+20/-2)
To merge this branch: bzr merge lp:~adiroiban/pocket-lint/986239
Reviewer Review Type Date Requested Status
Curtis Hovey code Approve
Review via email: mp+102977@code.launchpad.net

Description of the change

Hi,

This is my initial rst checker.

It contains a few changes outside of the rst part.

1. For the test runner I added the option to select the test based on regex. In this was I was able to run only a subset of test modules.

2. For the general CheckerTestCase, i set self.reporter.call_count = 0 in the setUp. Not sure if this is really needed.

Please let me know if you want me to revert these changes.
----

Now for the RST part.

For the Language class, I wanted to sort the language type identifier objects, mimetypes.add_type and mime_type_language in alphabetic order... but I didn't want to add to much noise to the diff.

I know that it is hard to force a certain coding convention and the current rules are made base on the style used in the Python documentation.

It does not checks for rst compilation errors, but rather for coding conventions.

For not it only checks for sections styles and an empty line at the end of file.

Please let me know if you thinks this is on a right direction and what should be changed.

Thanks!
Adi

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you very much.

I have merged this and pushed it to Launchpad. I think there are a few methods that return True or False that could be made smaller and more precise. I do not think they need changing just yet. I think it is best that this feature be merged and used so that we know exactly where we want to improve it.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'pocketlint/formatcheck.py'
2--- pocketlint/formatcheck.py 2012-04-20 16:52:00 +0000
3+++ pocketlint/formatcheck.py 2012-04-21 17:17:20 +0000
4@@ -178,6 +178,7 @@
5 DOCBOOK = object()
6 LOG = object()
7 SQL = object()
8+ RESTRUCTUREDTEXT = object()
9
10 XML_LIKE = (XML, XSLT, HTML, ZPT, ZCML, DOCBOOK)
11
12@@ -186,6 +187,7 @@
13 mimetypes.add_type('text/x-python-doctest', '.doctest')
14 mimetypes.add_type('text/x-twisted-application', '.tac')
15 mimetypes.add_type('text/x-log', '.log')
16+ mimetypes.add_type('text/x-rst', '.rst')
17 mime_type_language = {
18 'text/x-python': PYTHON,
19 'text/x-twisted-application': PYTHON,
20@@ -195,6 +197,7 @@
21 'text/plain': TEXT,
22 'text/x-sql': SQL,
23 'text/x-log': LOG,
24+ 'text/x-rst': RESTRUCTUREDTEXT,
25 'application/javascript': JAVASCRIPT,
26 'application/xml': XML,
27 'application/x-sh': SH,
28@@ -648,6 +651,248 @@
29 self.check_tab(line_no, line)
30
31
32+class ReStructuredTextChecker(BaseChecker, AnyTextMixin):
33+ """Check reStructuredText ource code."""
34+
35+ # Taken from rst documentation.
36+ delimiter_characters = [
37+ '=', '-', '`', ':', '\'', '"', '~', '^', '_', '*', '+', '#', '<', '>',
38+ ]
39+
40+ def __init__(self, file_path, text, reporter=None):
41+ super(ReStructuredTextChecker, self).__init__(
42+ file_path, text, reporter=reporter)
43+ self.lines = self.text.splitlines()
44+
45+ def check(self):
46+ """Check the syntax of the reStructuredText code."""
47+ self.check_lines()
48+ self.check_empty_last_line()
49+
50+ def check_lines(self):
51+ """Call each line checker for each line in text."""
52+ for line_no, line in enumerate(self.lines):
53+ line_no += 1
54+ self.check_length(line_no, line)
55+ self.check_trailing_whitespace(line_no, line)
56+ self.check_tab(line_no, line)
57+ self.check_conflicts(line_no, line)
58+
59+ if self.isTransition(line_no - 1):
60+ self.check_transition(line_no - 1)
61+ elif self.isSectionDelimiter(line_no - 1):
62+ self.check_section_delimiter(line_no - 1)
63+ else:
64+ pass
65+
66+ def isTransition(self, line_number):
67+ '''Return True if the current line is a line transition.'''
68+ line = self.lines[line_number]
69+ if len(line) < 4:
70+ return False
71+
72+ if len(self.lines) < 3:
73+ return False
74+
75+ succesive_characters = (
76+ line[0] == line[1] == line[2] == line[3] and
77+ line[0] in self.delimiter_characters)
78+
79+ if not succesive_characters:
80+ return False
81+
82+ emply_lines_bounded = (
83+ self.lines[line_number - 1] == '' and
84+ self.lines[line_number + 1] == '')
85+
86+ if not emply_lines_bounded:
87+ return False
88+
89+ return True
90+
91+ def check_transition(self, line_number):
92+ '''Transitions should be delimited by a single emtpy line.'''
93+ if (self.lines[line_number - 2] == '' or
94+ self.lines[line_number + 2] == ''):
95+ self.message(
96+ line_number + 1,
97+ 'Transition markers should be bounded by single empty lines.',
98+ icon='info',
99+ )
100+
101+ def isSectionDelimiter(self, line_number):
102+ '''Return true if the line is a section delimiter.'''
103+ if len(self.lines) < 3:
104+ return False
105+
106+ if line_number >= len(self.lines):
107+ return False
108+
109+ line = self.lines[line_number]
110+ if len(line) < 3:
111+ return False
112+
113+ if (line[0] == line[1] == line[2] and line[0] in
114+ self.delimiter_characters):
115+ if ' ' in line:
116+ # We have a table header.
117+ return False
118+ else:
119+ return True
120+
121+ return False
122+
123+ def check_section_delimiter(self, line_number):
124+ """Checks for section delimiter.
125+
126+ These checkes are designed for sections delimited by top and bottom
127+ markers.
128+
129+ ======= <- top marker
130+ Section <- text_line
131+ ======= <- bottom marker
132+
133+ If the section is delimted only by bottom marker, the section text
134+ is considered the top marker.
135+
136+ Section <- top marker, text_line
137+ ======= <- bottom marker
138+
139+ If the section has a custom anchor name:
140+
141+ .. _link <- top marker
142+
143+ =======
144+ Section <- text_line
145+ ======= <- bottom marker
146+
147+ or:
148+
149+ .. _link <- top marker
150+
151+ Section <- text_line
152+ ======= <- bottom marker
153+
154+ If we have top and bottom markers, the check will be called twice (
155+ for each marker). In this case we will skip the tests for bottom
156+ marker.
157+ """
158+ human_line_number = line_number + 1
159+ current_line = self.lines[line_number]
160+
161+ # Skip test if we have both top and bottom markers and we are
162+ # at the bottom marker.
163+ if (line_number > 1 and current_line == self.lines[line_number - 2]):
164+ return
165+
166+ if ((line_number + 2) < len(self.lines) and
167+ current_line == self.lines[line_number + 2]):
168+ # We have both top and bottom markers and we are currently at
169+ # the top marker.
170+ top_marker = line_number
171+ text_line = line_number + 1
172+ bottom_marker = line_number + 2
173+ else:
174+ # We only have bottom marker, and are at the bottom marker.
175+ top_marker = line_number - 1
176+ text_line = line_number - 1
177+ bottom_marker = line_number
178+
179+ # In case we have a custom anchor, the top_marker is replaced by
180+ # the custom anchor.
181+ if self._sectionHasCustomAnchor(top_marker):
182+ top_marker = top_marker - 2
183+
184+ # Check underline length for bottom marker,
185+ # since top marker can be the same as text line.
186+ if len(self.lines[bottom_marker]) != len(self.lines[text_line]):
187+ self.message(
188+ human_line_number,
189+ 'Section marker has wrong length.',
190+ icon='error',
191+ )
192+
193+ if not self._haveGoodSpacingBeforeSection(top_marker):
194+ self.message(
195+ human_line_number,
196+ 'Section should be divided by 2 empty lines.',
197+ icon='info',
198+ )
199+
200+ if not self._haveGoodSpacingAfterSection(bottom_marker):
201+ self.message(
202+ human_line_number,
203+ 'Section title should be followed by 1 empty line.',
204+ icon='info',
205+ )
206+
207+ def _sectionHasCustomAnchor(self, top_marker):
208+ if (top_marker - 2) < 0:
209+ return False
210+
211+ if self.lines[top_marker - 2].startswith('.. _'):
212+ return True
213+
214+ return False
215+
216+ def _haveGoodSpacingBeforeSection(self, top_marker):
217+ '''Return True if we have good spacing before the section.'''
218+ if top_marker > 0:
219+ if self.lines[top_marker - 1] != '':
220+ return False
221+
222+ # If we are on the second line, there is no space for 2 empty lines
223+ # before.
224+ if top_marker == 1:
225+ return False
226+
227+ if top_marker > 1:
228+ if self.lines[top_marker - 2] != '':
229+ return False
230+
231+ if top_marker > 2:
232+ if self.lines[top_marker - 3] == '':
233+ return False
234+
235+ return True
236+
237+ def _haveGoodSpacingAfterSection(self, bottom_marker):
238+ '''Return True if we have good spacing after the section.'''
239+ lines_count = len(self.lines)
240+
241+ if bottom_marker < lines_count - 1:
242+ if self.lines[bottom_marker + 1] != '':
243+ return False
244+
245+ if bottom_marker < lines_count - 2:
246+ if self.lines[bottom_marker + 2] == '':
247+ # If the section is followed by 2 empty spaces and then
248+ # followed by a section delimiter, the section delimiter
249+ # rules will take priority
250+ if self.isSectionDelimiter(bottom_marker + 3):
251+ return True
252+ if self.isSectionDelimiter(bottom_marker + 4):
253+ return True
254+ return False
255+
256+ return True
257+
258+ def check_empty_last_line(self):
259+ """Chech the files ends with an emtpy line and not with double empty
260+ line.
261+
262+ This will avoid merge conflicts.
263+ """
264+ if len(self.lines) < 2:
265+ return
266+ if self.text[-1] != '\n' or self.text[-2:] == '\n\n':
267+ self.message(
268+ len(self.lines),
269+ 'File does not ends with an empty line.',
270+ icon='info',
271+ )
272+
273+
274 def get_option_parser():
275 """Return the option parser for this program."""
276 usage = "usage: %prog [options] file1 file2"
277
278=== modified file 'pocketlint/tests/__init__.py'
279--- pocketlint/tests/__init__.py 2012-01-29 21:24:20 +0000
280+++ pocketlint/tests/__init__.py 2012-04-21 17:17:20 +0000
281@@ -11,3 +11,4 @@
282
283 def setUp(self):
284 self.reporter = Reporter(Reporter.COLLECTOR)
285+ self.reporter.call_count = 0
286
287=== added file 'pocketlint/tests/test_restructuredtext.py'
288--- pocketlint/tests/test_restructuredtext.py 1970-01-01 00:00:00 +0000
289+++ pocketlint/tests/test_restructuredtext.py 2012-04-21 17:17:20 +0000
290@@ -0,0 +1,524 @@
291+'''Tests for ReStructuredTextChecker.'''
292+from pocketlint.formatcheck import ReStructuredTextChecker
293+from pocketlint.tests import CheckerTestCase
294+
295+# This is a valid rst content.
296+# This comment is here so that the content starts at line 11
297+# and make it easier to identify errors in tests.
298+# Just add 10 to the reported line number.
299+#
300+valid_rst_content = '''\
301+=============
302+First section
303+=============
304+
305+Text *for* first **section**.
306+
307+
308+--------------------
309+Second emtpy section
310+--------------------
311+
312+
313+Third section
314+^^^^^^^^^^^^^
315+
316+Paragrhap for
317+third section `with link<http://my.home>`_.
318+
319+::
320+
321+ Literal block1.
322+
323+ Literal block paragraph.
324+
325+
326+| Line blocks are useful for addresses,
327+| verse, and adornment-free lists.
328+
329+
330+.. _section-permalink:
331+
332+Another Section with predefined link
333+------------------------------------
334+
335+>>> print "This is a doctest block."
336+... with a line continuation
337+This is a doctest block.
338+
339+A grid table.
340+
341++------------+------------+-----------+
342+| Header 1 | Header 2 | Header 3 |
343++============+============+===========+
344+| body row 1 | column 2 | column 3 |
345++------------+------------+-----------+
346+| body row 2 | Cells may span columns.|
347++------------+------------+-----------+
348+| body row 3 | Cells may | - Cells |
349++------------+ span rows. | - contain |
350+| body row 4 | | - blocks. |
351++------------+------------+-----------+
352+
353+A simple table.
354+
355+===== ===== ======
356+ Inputs Output
357+------------ ------
358+ A B A or B
359+===== ===== ======
360+False False False
361+True False True
362+False True True
363+True True True
364+===== ===== ======
365+
366+A transition marker is a horizontal line
367+of 4 or more repeated punctuation
368+characters.
369+
370+------------
371+
372+A transition should not begin or end a
373+section or document, nor should two
374+transitions be immediately adjacent.
375+
376+Footnote references, like [5]_.
377+Note that footnotes may get
378+rearranged, e.g., to the bottom of
379+the "page".
380+
381+.. [5] A numerical footnote. Note
382+ there's no colon after the ``]``.
383+
384+External hyperlinks, like Python_.
385+.. _Python: http://www.python.org/
386+
387+For instance:
388+
389+.. image:: images/ball1.gif
390+
391+The |biohazard| symbol must be used on containers used to dispose of
392+medical waste.
393+
394+.. |biohazard| image:: biohazard.png
395+
396+.. This text will not be shown
397+ (but, for instance, in HTML might be
398+ rendered as an HTML comment)
399+'''
400+# The last line from multi line string is a bit hard to visualize,
401+# but it is there.
402+
403+
404+class TestReStructuredTextChecker(CheckerTestCase):
405+ """Verify reStructuredTextChecker checking."""
406+
407+ def test_empty_file(self):
408+ self.reporter.call_count = 0
409+ content = ('')
410+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
411+ checker.check()
412+ self.assertEqual([], self.reporter.messages)
413+ self.assertEqual(0, self.reporter.call_count)
414+
415+ def test_valid_content(self):
416+ self.reporter.call_count = 0
417+ checker = ReStructuredTextChecker(
418+ 'bogus', valid_rst_content, self.reporter)
419+ checker.check()
420+ self.assertEqual([], self.reporter.messages)
421+ self.assertEqual(0, self.reporter.call_count)
422+
423+ def test_no_empty_last_line(self):
424+ self.reporter.call_count = 0
425+ content = (
426+ 'Some first line\n'
427+ 'the second and last line witout newline'
428+ )
429+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
430+ checker.check_empty_last_line()
431+ expected = [(
432+ 2, 'File does not ends with an empty line.')]
433+ self.assertEqual(expected, self.reporter.messages)
434+ self.assertEqual(1, self.reporter.call_count)
435+
436+ def test_multiple_empty_last_lines(self):
437+ self.reporter.call_count = 0
438+ content = (
439+ 'Some first line\n'
440+ 'the second and last\n'
441+ '\n'
442+ )
443+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
444+ checker.check_empty_last_line()
445+ expected = [(
446+ 3, 'File does not ends with an empty line.')]
447+ self.assertEqual(expected, self.reporter.messages)
448+ self.assertEqual(1, self.reporter.call_count)
449+
450+ def test_isTransition_good(self):
451+ content = (
452+ '\n'
453+ '----\n'
454+ '\n'
455+ )
456+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
457+ result = checker.isTransition(1)
458+ self.assertTrue(result)
459+
460+ def test_isTransition_short_line(self):
461+ content = (
462+ '\n'
463+ '---\n'
464+ '\n'
465+ )
466+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
467+ result = checker.isTransition(1)
468+ self.assertFalse(result)
469+
470+ def test_isTransition_short_file(self):
471+ content = (
472+ '\n'
473+ '----\n'
474+ ''
475+ )
476+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
477+ result = checker.isTransition(1)
478+ self.assertFalse(result)
479+
480+ def test_isTransition_false(self):
481+ content = (
482+ '\n'
483+ '----\n'
484+ 'some\n'
485+ )
486+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
487+ result = checker.isTransition(1)
488+ self.assertFalse(result)
489+
490+ content = (
491+ 'some\n'
492+ '----\n'
493+ '\n'
494+ )
495+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
496+ result = checker.isTransition(1)
497+ self.assertFalse(result)
498+
499+ def test_check_transitions_good(self):
500+ content = (
501+ 'some text\n'
502+ '\n'
503+ '----\n'
504+ '\n'
505+ 'some text\n'
506+ )
507+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
508+ checker.check_transition(2)
509+ self.assertEqual([], self.reporter.messages)
510+ self.assertEqual(0, self.reporter.call_count)
511+
512+ def test_check_transitions_bad_spacing_before(self):
513+ content = (
514+ 'some text\n'
515+ '\n'
516+ '\n'
517+ '----\n'
518+ '\n'
519+ 'some text\n'
520+ )
521+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
522+ checker.check_transition(3)
523+ expect = [(
524+ 4, 'Transition markers should be bounded by single empty lines.')]
525+ self.assertEqual(expect, self.reporter.messages)
526+ self.assertEqual(1, self.reporter.call_count)
527+
528+ def test_check_transitions_bad_spacing_after(self):
529+ content = (
530+ 'some text\n'
531+ '\n'
532+ '----\n'
533+ '\n'
534+ '\n'
535+ 'some text\n'
536+ )
537+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
538+ checker.check_transition(2)
539+ expect = [(
540+ 3, 'Transition markers should be bounded by single empty lines.')]
541+ self.assertEqual(expect, self.reporter.messages)
542+ self.assertEqual(1, self.reporter.call_count)
543+
544+ def test_check_transitions_bad_spacing_both(self):
545+ content = (
546+ 'some text\n'
547+ '\n'
548+ '\n'
549+ '----\n'
550+ '\n'
551+ '\n'
552+ 'some text\n'
553+ )
554+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
555+ checker.check_transition(3)
556+ expect = [(
557+ 4, 'Transition markers should be bounded by single empty lines.')]
558+ self.assertEqual(expect, self.reporter.messages)
559+ self.assertEqual(1, self.reporter.call_count)
560+
561+ def test_isSectionDelimiter_short_file(self):
562+ content = (
563+ 'Something'
564+ '---------\n'
565+ ''
566+ )
567+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
568+ result = checker.isSectionDelimiter(1)
569+ self.assertFalse(result)
570+
571+ def test_isSectionDelimiter_short_line(self):
572+ content = (
573+ 'Som'
574+ '---\n'
575+ ''
576+ )
577+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
578+ result = checker.isSectionDelimiter(1)
579+ self.assertFalse(result)
580+
581+ def test_isSectionDelimiter_table(self):
582+ content = (
583+ '---- ----'
584+ 'Row1 Row1'
585+ '---- ----\n'
586+ ''
587+ )
588+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
589+ result = checker.isSectionDelimiter(0)
590+ self.assertFalse(result)
591+ result = checker.isSectionDelimiter(2)
592+ self.assertFalse(result)
593+
594+ def test_isSectionDelimiter_good(self):
595+ content = (
596+ 'Section\n'
597+ '-------\n'
598+ 'some text'
599+ )
600+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
601+ result = checker.isSectionDelimiter(1)
602+ self.assertTrue(result)
603+
604+ def test_isSectionDelimiter_good_bounded_start_of_file(self):
605+ content = (
606+ '=======\n'
607+ 'Section\n'
608+ '=======\n'
609+ )
610+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
611+ result = checker.isSectionDelimiter(0)
612+ self.assertTrue(result)
613+ result = checker.isSectionDelimiter(2)
614+ self.assertTrue(result)
615+
616+ def test_check_section_delimiter_bounded(self):
617+ content = (
618+ 'some text\n'
619+ '\n'
620+ '\n'
621+ '=======\n'
622+ 'Section\n'
623+ '=======\n'
624+ '\n'
625+ 'some text\n'
626+ )
627+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
628+ checker.check_section_delimiter(3)
629+ checker.check_section_delimiter(5)
630+ self.assertEqual([], self.reporter.messages)
631+ self.assertEqual(0, self.reporter.call_count)
632+
633+ def test_check_section_delimiter_bad_marker_length(self):
634+ content = (
635+ 'Section\n'
636+ '------\n'
637+ '\n'
638+ 'some text\n'
639+ 'other text\n'
640+ )
641+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
642+ checker.check_section_delimiter(1)
643+ expect = [(2, 'Section marker has wrong length.')]
644+ self.assertEqual(expect, self.reporter.messages)
645+ self.assertEqual(1, self.reporter.call_count)
646+
647+ def test_check_section_delimiter_bad_length_both_markers(self):
648+ content = (
649+ '---------\n'
650+ 'Section\n'
651+ '---------\n'
652+ '\n'
653+ 'some text\n'
654+ 'other text\n'
655+ )
656+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
657+ checker.check_section_delimiter(0)
658+ checker.check_section_delimiter(2)
659+ expect = [(1, 'Section marker has wrong length.')]
660+ self.assertEqual(expect, self.reporter.messages)
661+ self.assertEqual(1, self.reporter.call_count)
662+
663+ def test_check_section_before_space_good_start_both(self):
664+ content = (
665+ '-------\n'
666+ 'Section\n'
667+ '-------\n'
668+ '\n'
669+ )
670+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
671+ checker.check_section_delimiter(0)
672+ checker.check_section_delimiter(2)
673+ self.assertEqual([], self.reporter.messages)
674+ self.assertEqual(0, self.reporter.call_count)
675+
676+ def test_check_section_before_space_good_start_bottom(self):
677+ content = (
678+ 'Section\n'
679+ '-------\n'
680+ '\n'
681+ )
682+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
683+ checker.check_section_delimiter(1)
684+ self.assertEqual([], self.reporter.messages)
685+ self.assertEqual(0, self.reporter.call_count)
686+
687+ def test_check_section_before_space_bad_only_one_line_near_start(self):
688+ content = (
689+ '\n'
690+ 'Section\n'
691+ '-------\n'
692+ '\n'
693+ )
694+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
695+ checker.check_section_delimiter(2)
696+ expect = [(3, 'Section should be divided by 2 empty lines.')]
697+ self.assertEqual(expect, self.reporter.messages)
698+ self.assertEqual(1, self.reporter.call_count)
699+
700+ def test_check_section_before_space_bad_only_one_line(self):
701+ content = (
702+ 'end of previous section.\n'
703+ '\n'
704+ 'Section\n'
705+ '-------\n'
706+ '\n'
707+ )
708+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
709+ checker.check_section_delimiter(3)
710+ expect = [(4, 'Section should be divided by 2 empty lines.')]
711+ self.assertEqual(expect, self.reporter.messages)
712+ self.assertEqual(1, self.reporter.call_count)
713+
714+ def test_check_section_before_space_multiple_empty_lines(self):
715+ content = (
716+ 'end of previous section.\n'
717+ '\n'
718+ '\n'
719+ '\n'
720+ 'Section\n'
721+ '-------\n'
722+ '\n'
723+ )
724+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
725+ checker.check_section_delimiter(5)
726+ expect = [(6, 'Section should be divided by 2 empty lines.')]
727+ self.assertEqual(expect, self.reporter.messages)
728+ self.assertEqual(1, self.reporter.call_count)
729+
730+ def test_check_section_after_space_last_line(self):
731+ content = (
732+ 'Section\n'
733+ '-------\n'
734+ )
735+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
736+ checker.check_section_delimiter(1)
737+ self.assertEqual([], self.reporter.messages)
738+ self.assertEqual(0, self.reporter.call_count)
739+
740+ def test_check_section_after_space_bad(self):
741+ content = (
742+ 'Section\n'
743+ '-------\n'
744+ 'Paragraph start.\n'
745+ )
746+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
747+ checker.check_section_delimiter(1)
748+ expect = [(2, 'Section title should be followed by 1 empty line.')]
749+ self.assertEqual(expect, self.reporter.messages)
750+ self.assertEqual(1, self.reporter.call_count)
751+
752+ def test_check_section_after_space_too_many_empty_lines(self):
753+ content = (
754+ 'Section\n'
755+ '-------\n'
756+ '\n'
757+ '\n'
758+ 'Paragraph start.\n'
759+ )
760+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
761+ checker.check_section_delimiter(1)
762+ expect = [(2, 'Section title should be followed by 1 empty line.')]
763+ self.assertEqual(expect, self.reporter.messages)
764+ self.assertEqual(1, self.reporter.call_count)
765+
766+ def test_check_section_empty_section_next_section_only_bottom(self):
767+ content = (
768+ 'Emtpy Section\n'
769+ '=============\n'
770+ '\n'
771+ '\n'
772+ 'Another Section\n'
773+ '---------------\n'
774+ )
775+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
776+ checker.check_section_delimiter(1)
777+ self.assertEqual([], self.reporter.messages)
778+ self.assertEqual(0, self.reporter.call_count)
779+
780+ def test_check_section_empty_section_next_section_both_markers(self):
781+ content = (
782+ 'Emtpy Section\n'
783+ '=============\n'
784+ '\n'
785+ '\n'
786+ '---------------\n'
787+ 'Another Section\n'
788+ '---------------\n'
789+ )
790+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
791+ checker.check_section_delimiter(1)
792+ self.assertEqual([], self.reporter.messages)
793+ self.assertEqual(0, self.reporter.call_count)
794+
795+ def disable_check_section_delimiter_both_markers_not_sync(self):
796+ # When both top and bottom markers are used, and they don't have
797+ # the same size, they are interpreted as separate markers.
798+ content = (
799+ '------\n'
800+ 'Section\n'
801+ '--------\n'
802+ '\n'
803+ 'some text\n'
804+ 'other text\n'
805+ )
806+ checker = ReStructuredTextChecker('bogus', content, self.reporter)
807+ checker.check_lines()
808+ expect = [
809+ (1, 'Section marker has wrong length.'),
810+ (1, 'Section title should be followed by 1 empty line.'),
811+ (3, 'Section marker has wrong length.'),
812+ ]
813+ self.assertEqual(expect, self.reporter.messages)
814+ self.assertEqual(3, self.reporter.call_count)
815
816=== modified file 'test.py'
817--- test.py 2012-01-29 21:24:20 +0000
818+++ test.py 2012-04-21 17:17:20 +0000
819@@ -6,6 +6,7 @@
820
821 import re
822 import os
823+import sys
824 import unittest
825 try:
826 from unittest.runner import _WritelnDecorator
827@@ -81,23 +82,40 @@
828 self.stream.write(text)
829
830
831-def find_tests(root_dir):
832+def find_tests(root_dir, filter=None):
833 """Generate a list of matching test modules below a directory."""
834 for path, subdirs, files in os.walk(root_dir):
835 subdirs[:] = [dir for dir in subdirs]
836 if path.endswith('tests'):
837 for file_ in files:
838 if file_.startswith('test_') and file_.endswith('.py'):
839+ if filter and not re.search(filter, file_):
840+ continue
841 file_path = os.path.join(path, file_)
842 test_module = file_path[2:-3].replace('/', '.')
843 yield test_module
844
845
846+def show_help():
847+ print '''\
848+python test.py [-h|--help] [test_module_regex_filter]
849+'''
850+
851+
852 def main():
853+ if len(sys.argv) > 1:
854+ if (sys.argv[1] in ['-h', '--help']):
855+ show_help()
856+ return
857+ else:
858+ filter = sys.argv[1]
859+ else:
860+ filter = None
861+
862 unittest.runner._WritelnDecorator = XTermWritelnDecorator
863 test_loader = unittest.defaultTestLoader
864 suite = unittest.TestSuite()
865- for test_module in find_tests('.'):
866+ for test_module in find_tests('.', filter=filter):
867 suite.addTest(test_loader.loadTestsFromName(test_module))
868 unittest.TextTestRunner(verbosity=2).run(suite)
869

Subscribers

People subscribed via source and target branches

to all changes: