44import re
55import traceback
66import urllib2 as urllib
7+ from urlparse import urljoin
78from datetime import datetime
89
9- from ietf .utils import soup2text as html2text
10+ from ietf .utils . soup2text import TextSoup
1011from difflib import unified_diff
1112
1213import django .test .simple
@@ -25,18 +26,50 @@ def run_tests(module_list, verbosity=0, extra_tests=[]):
2526 # during the search for a 'tests' module ...
2627 return django .test .simple .run_tests (module_list , 0 , extra_tests )
2728
28- def reduce_text (html , pre = False , fill = True ):
29+ def normalize_html (html , fill ):
30+ # Line ending normalization
31+ html = html .replace ("\r \n " , "\n " ).replace ("\r " , "\n " )
32+ # remove comments
33+ html = re .sub ("(?s)<!--.*?-->" , "" , html )
34+ # attempt to close <li>s (avoid too deep recursion later)
2935 if html .count ("<li>" ) > 5 * html .count ("</li>" ):
3036 html = html .replace ("<li>" , "</li><li>" )
3137 if not fill :
3238 html = re .sub ("<br ?/?>" , "<br/><br/>" , html )
3339 html = re .sub (r"(?i)(RFC) (\d+)" , r"\1\2" , html ) # ignore "RFC 1234" vs. "RFC1234" diffs
3440 html = re .sub (r"\bID\b" , r"I-D" , html ) # idnore " ID " vs. " I-D " diffs
35- text = html2text (html , pre = pre , fill = fill ).strip ()
41+ # some preprocessing to handle common pathological cases
42+ html = re .sub ("<br */?>[ \t \n ]*(<br */?>)+" , "<p/>" , html )
43+ html = re .sub ("<br */?>([^\n ])" , r"<br />\n\1" , html )
44+ return html
45+
46+ def reduce_text (html , pre = False , fill = True ):
47+ html = normalize_html (html , fill )
48+ page = TextSoup (html )
49+ text = page .as_text (encoding = 'latin-1' , pre = pre , fill = fill ).strip ()
3650 text = text .replace (" : " , ": " ).replace (" :" , ": " )
3751 text = text .replace ('."' , '".' )
3852 text = text .replace (',"' , '",' )
39- return text
53+ return text , page
54+
55+ def update_reachability (url , code , page ):
56+ if code in ["301" , "302" , "303" , "307" ]:
57+ try :
58+ file = urllib .urlopen (url )
59+ html = file .read ()
60+ file .close ()
61+ code = 200
62+ page = TextSoup (html )
63+ except urllib .URLError , e :
64+ note (" Error retrieving %s: %s" % (url , e ))
65+ code = e .code
66+ page = None
67+ module .reachability [url ] = (code , "Test" )
68+ links = ( [ urljoin (url , a ["href" ]) for a in page .findAll ("a" ) ]
69+ + [ urljoin (url , img ["src" ]) for img in page .findAll ("img" ) ] )
70+ for link in links :
71+ if not link in module .reachability :
72+ module .reachability [link ] = (None , url )
4073
4174def lines (text , pre = False ):
4275 if pre :
@@ -126,7 +159,7 @@ def note(string):
126159 """Like a print function, but adds a leading timestamp line"""
127160 now = datetime .utcnow ()
128161 print string
129- print now .strftime (" %Y-%m-%d_%H:%M" ), "+%ds " % (now - prev_note_time ).seconds
162+ print now .strftime (" %Y-%m-%d_%H:%M" ), "+%3.1f " % (now - prev_note_time ).seconds
130163 prev_note_time = datetime .utcnow ()
131164
132165def module_setup (module ):
@@ -140,6 +173,7 @@ def module_setup(module):
140173 module .ignores = {}
141174 module .testtuples = get_testurls ()
142175 module .testurls = [ tuple [1 ] for tuple in module .testtuples ]
176+ module .reachability = {}
143177
144178 # find diff chunks
145179 testdir = os .path .abspath (settings .BASE_DIR + "/../test/diff/" )
@@ -188,7 +222,9 @@ class UrlTestCase(TestCase):
188222 def __init__ (self , * args , ** kwargs ):
189223 TestCase .__init__ (self , * args , ** kwargs )
190224
191-
225+ # ------------------------------------------------------------------------------
226+ # Setup and tear-down
227+
192228 def setUp (self ):
193229 from django .test .client import Client
194230 self .client = Client ()
@@ -204,6 +240,13 @@ def tearDown(self):
204240 settings .DATABASE_NAME = self .testdb
205241 connection .cursor ()
206242
243+ # ------------------------------------------------------------------------------
244+ # Test methods.
245+ #
246+ # These are listed in alphabetic order, which is the order in which they will
247+ # be executed.
248+ #
249+
207250 def testCoverage (self ):
208251 covered = []
209252 for codes , testurl , goodurl in module .testtuples :
@@ -226,6 +269,55 @@ def testCoverage(self):
226269 print "All the application URL patterns seem to have test cases."
227270 #print "Not all the application URLs has test cases."
228271
272+ def testRedirectsList (self ):
273+ note ("\n Testing specified Redirects:" )
274+ self .doRedirectsTest (module .testtuples )
275+
276+ def testUrlsFallback (self ):
277+ note ("\n Fallback: Test access to URLs which don't have an explicit test entry:" )
278+ lst = []
279+ for pattern in module .patterns :
280+ if pattern .startswith ("^" ) and pattern .endswith ("$" ):
281+ url = "/" + pattern [1 :- 1 ]
282+ # if there is no variable parts in the url, test it
283+ if re .search ("^[-a-z0-9./_]*$" , url ) and not url in module .testurls and not url .startswith ("/admin/" ):
284+ lst .append ((["200" ], url , None ))
285+ else :
286+ #print "No fallback test for %s" % (url)
287+ pass
288+ else :
289+ lst .append ((["Skip" ], pattern , None ))
290+ self .doUrlsTest (lst )
291+
292+ def testUrlsList (self ):
293+ note ("\n Testing specified URLs:" )
294+ self .doUrlsTest (module .testtuples )
295+
296+ def testUrlsReachability (self ):
297+ # This test should be sorted after the other tests which retrieve URLs
298+ note ("\n Testing URL reachability:" )
299+ for url in module .reachability :
300+ code , source = module .reachability [url ]
301+ if not code :
302+ if url .startswith ("/" ):
303+ baseurl , args = split_url (url )
304+ code = str (self .client .get (baseurl , args ).status_code )
305+ elif url .startswith ("mailto:" ):
306+ continue
307+ else :
308+ try :
309+ file = urllib .urlopen (url )
310+ file .close ()
311+ code = "200"
312+ except urllib .HTTPError , e :
313+ code = str (e .code )
314+ if not code in ["200" ]:
315+ note ("Reach %5s %s (from %s)" % (code , url , source ))
316+
317+
318+ # ------------------------------------------------------------------------------
319+ # Worker methods
320+
229321 def doRedirectsTest (self , lst ):
230322 response_count = {}
231323 for codes , url , master in lst :
@@ -311,11 +403,12 @@ def doUrlsTest(self, lst):
311403 try :
312404 if goodhtml and response .content :
313405 if "sort" in codes :
314- testtext = reduce_text (response .content , fill = False )
315- goodtext = reduce_text (goodhtml , fill = False )
406+ testtext , testpage = reduce_text (response .content , fill = False )
407+ goodtext , goodpage = reduce_text (goodhtml , fill = False )
316408 else :
317- testtext = reduce_text (response .content )
318- goodtext = reduce_text (goodhtml )
409+ testtext , testpage = reduce_text (response .content )
410+ goodtext , goodpage = reduce_text (goodhtml )
411+ update_reachability (url , code , testpage )
319412 # Always ignore some stuff
320413 for regex in module .ignores ["always" ]:
321414 testtext = re .sub (regex , "" , testtext )
@@ -405,30 +498,6 @@ def doUrlsTest(self, lst):
405498 if response_count :
406499 print ""
407500
408- def testUrlsList (self ):
409- note ("\n Testing specified URLs:" )
410- self .doUrlsTest (module .testtuples )
411-
412- def testRedirectsList (self ):
413- note ("\n Testing specified Redirects:" )
414- self .doRedirectsTest (module .testtuples )
415-
416- def testUrlsFallback (self ):
417- note ("\n Fallback: Test access to URLs which don't have an explicit test entry:" )
418- lst = []
419- for pattern in module .patterns :
420- if pattern .startswith ("^" ) and pattern .endswith ("$" ):
421- url = "/" + pattern [1 :- 1 ]
422- # if there is no variable parts in the url, test it
423- if re .search ("^[-a-z0-9./_]*$" , url ) and not url in module .testurls and not url .startswith ("/admin/" ):
424- lst .append ((["200" ], url , None ))
425- else :
426- #print "No fallback test for %s" % (url)
427- pass
428- else :
429- lst .append ((["Skip" ], pattern , None ))
430-
431- self .doUrlsTest (lst )
432501
433502
434503class Module :
0 commit comments