Skip to content

Commit b70edf9

Browse files
committed
Add reachability testing for URLs in the app web-pages.
- Legacy-Id: 848
1 parent d7dfca4 commit b70edf9

2 files changed

Lines changed: 103 additions & 42 deletions

File tree

ietf/tests.py

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import re
55
import traceback
66
import urllib2 as urllib
7+
from urlparse import urljoin
78
from datetime import datetime
89

9-
from ietf.utils import soup2text as html2text
10+
from ietf.utils.soup2text import TextSoup
1011
from difflib import unified_diff
1112

1213
import 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

4174
def 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

132165
def 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("\nTesting specified Redirects:")
274+
self.doRedirectsTest(module.testtuples)
275+
276+
def testUrlsFallback(self):
277+
note("\nFallback: 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("\nTesting 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("\nTesting 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("\nTesting specified URLs:")
410-
self.doUrlsTest(module.testtuples)
411-
412-
def testRedirectsList(self):
413-
note("\nTesting specified Redirects:")
414-
self.doRedirectsTest(module.testtuples)
415-
416-
def testUrlsFallback(self):
417-
note("\nFallback: 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

434503
class Module:

ietf/utils/soup2text.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ def as_text(self, encoding='latin-1', pre=False, fill=True, clean=True):
110110
str = re.sub("[ \t]+", " ", str)
111111
str = re.sub("\n\n+", "\n\n", str)
112112
return str
113-
114113

115114
def __str__(self, encoding='latin-1',
116115
prettyPrint=False, indentLevel=0):
@@ -121,13 +120,6 @@ def __str__(self, encoding='latin-1',
121120
return str
122121

123122
def soup2text(html, encoding='latin-1', pre=False, fill=True):
124-
# Line ending normalization
125-
html = html.replace("\r\n", "\n").replace("\r", "\n")
126-
# remove comments
127-
html = re.sub("(?s)<!--.*?-->", "", html)
128-
# some preprocessing to handle common pathological cases
129-
html = re.sub("<br */?>[ \t\n]*(<br */?>)+", "<p/>", html)
130-
html = re.sub("<br */?>([^\n])", r"<br />\n\1", html)
131123
soup = TextSoup(html)
132124
return soup.as_text(encoding, pre, fill)
133125

0 commit comments

Comments
 (0)