Make devel/test.py less repetitive.
[darkhttpd] / devel / test.py
1 #!/usr/bin/env python
2 import unittest
3 import socket
4 import signal
5 import re
6
7 class Conn:
8 def __init__(self):
9 self.port = 12346
10 self.s = socket.socket()
11 self.s.connect(("0.0.0.0", self.port))
12 # connect throws socket.error on connection refused
13
14 def get(self, url, http_ver=None, endl="\n",
15 req_hdrs={"User-Agent": "test.py"}):
16 req = "GET "+url
17 if http_ver is not None:
18 req += " HTTP/"+http_ver
19 req += "\n"
20 for k,v in req_hdrs.items():
21 req += k+": "+v+endl
22 req += endl # end of request
23 self.s.send(req)
24 ret = ""
25 while True:
26 signal.alarm(1) # don't wait forever
27 r = self.s.recv(65536)
28 signal.alarm(0)
29 if r == "":
30 break
31 else:
32 ret += r
33 return ret
34
35 def parse(resp):
36 """
37 Parse response into status line, headers and body.
38 """
39 pos = resp.index("\r\n\r\n") # throws exception on failure
40 head = resp[:pos]
41 body = resp[pos+4:]
42 status,head = head.split("\r\n", 1)
43 hdrs = {}
44 for line in head.split("\r\n"):
45 k, v = line.split(": ", 1)
46 hdrs[k] = v
47 return (status, hdrs, body)
48
49 class TestCases(unittest.TestCase):
50 def assertContains(self, body, *strings):
51 for s in strings:
52 self.assertTrue(s in body,
53 msg="expected %s in %s"%(repr(s), repr(body)))
54
55 def assertIsIndex(self, body, path):
56 self.assertContains(body,
57 "<title>%s</title>\n"%path,
58 "<h1>%s</h1>\n"%path,
59 '<a href="..">..</a>/',
60 'Generated by darkhttpd')
61
62 def assertIsInvalid(self, body, path):
63 self.assertContains(body,
64 "<title>400 Bad Request</title>",
65 "<h1>Bad Request</h1>\n",
66 "You requested an invalid URI: %s\n"%path,
67 'Generated by darkhttpd')
68
69 def nerf(s):
70 return re.sub("[^a-zA-Z0-9]", "_", s)
71
72 def makeCase(name, url, hdr_checker=None, body_checker=None,
73 req_hdrs={"User-Agent": "test.py"},
74 http_ver=None, endl="\n"):
75 def do_test(self):
76 resp = Conn().get(url, http_ver, endl, req_hdrs)
77 if http_ver is None:
78 status = ""
79 hdrs = {}
80 body = resp
81 else:
82 status, hdrs, body = parse(resp)
83
84 if hdr_checker is not None and http_ver is not None:
85 hdr_checker(self, hdrs)
86
87 if body_checker is not None:
88 body_checker(self, body)
89
90 # FIXME: check status
91 if http_ver is not None:
92 prefix = "HTTP/1.1 " # should 1.0 stay 1.0?
93 self.assertTrue(status.startswith(prefix),
94 msg="%s at start of %s"%(repr(prefix), repr(status)))
95
96 v = http_ver
97 if v is None:
98 v = "0.9"
99 test_name = "_".join([
100 "test",
101 nerf(name),
102 nerf("HTTP"+v),
103 {"\n":"LF", "\r\n":"CRLF"}[endl],
104 ])
105 setattr(TestCases, test_name, do_test)
106
107 def makeCases(name, url, hdr_checker=None, body_checker=None,
108 req_hdrs={"User-Agent": "test.py"}):
109 # FIXME: 0.9 is broken
110 #for http_ver in [None, "1.0", "1.1"]:
111 for http_ver in ["1.0", "1.1"]:
112 for endl in ["\n", "\r\n"]:
113 makeCase(name, url, hdr_checker, body_checker,
114 req_hdrs, http_ver, endl)
115
116 makeCases("index", "/", None,
117 lambda self,body: self.assertIsIndex(body, "/"))
118
119 makeCases("up dir", "/dir/../", None,
120 lambda self,body: self.assertIsIndex(body, "/dir/../"))
121
122 makeCases("extra slashes", "//dir///..////", None,
123 lambda self,body: self.assertIsIndex(body, "//dir///..////"))
124
125 makeCases("no trailing slash", "/dir/..", None,
126 lambda self,body: self.assertIsIndex(body, "/dir/.."))
127
128 makeCases("no leading slash", "dir/../", None,
129 lambda self,body: self.assertIsInvalid(body, "dir/../"))
130
131 makeCases("invalid up dir", "/../", None,
132 lambda self,body: self.assertIsInvalid(body, "/../"))
133
134 makeCases("fancy invalid up dir", "/./dir/./../../", None,
135 lambda self,body: self.assertIsInvalid(body, "/./dir/./../../"))
136
137 if __name__ == '__main__':
138 unittest.main()
139 #x = Conn().get("/xyz/../", "1.0")
140 #y = parse(x)
141 #print repr(y)
142
143 # vim:set ts=4 sw=4 et: