tools/httptester/server.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/tools/httptester/server.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,207 @@
     1.4 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +"""Regression testing HTTP 'server'
     1.9 +
    1.10 +The intent of this is to provide a (scriptable) framework for regression
    1.11 +testing mozilla stuff. See the docs for details.
    1.12 +"""
    1.13 +
    1.14 +__version__ = "0.1"
    1.15 +
    1.16 +import BaseHTTPServer
    1.17 +import string
    1.18 +import time
    1.19 +import os
    1.20 +from stat import *
    1.21 +import results
    1.22 +
    1.23 +class RegressionHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    1.24 +    server_version = "Regression/" + __version__
    1.25 +
    1.26 +    protocol_version = "HTTP/1.1"
    1.27 +
    1.28 +    def do_GET(self):
    1.29 +        if self.path == '/':
    1.30 +            return self.initial_redirect()
    1.31 +        if self.path[:1] != '/':
    1.32 +            return self.send_error(400,
    1.33 +                                   "Path %s does not begin with /" % `self.path`)
    1.34 +
    1.35 +        try:
    1.36 +            id, req = string.split(self.path[1:], '/', 1)
    1.37 +        except ValueError:
    1.38 +            return self.send_error(404, "Missing id and path")
    1.39 +
    1.40 +        if not req:
    1.41 +            # Initial request. Need to get a file list
    1.42 +            return self.list_tests(id)
    1.43 +        elif req == 'report':
    1.44 +            self.send_response(200)
    1.45 +            self.send_header('Content-Type', "text/plain")
    1.46 +            self.end_headers()
    1.47 +            res = results.results(id)
    1.48 +            res.write_report(self.wfile)
    1.49 +            del res
    1.50 +            return
    1.51 +        else:
    1.52 +            return self.handle_request(id, req)
    1.53 +            
    1.54 +    def initial_redirect(self):
    1.55 +        """Redirect the initial query.
    1.56 +
    1.57 +        I don't want to use cookies, because that would bring
    1.58 +        wallet into all the tests. So the url will be:
    1.59 +        http://foo/123456/path/and/filename.html"""
    1.60 +
    1.61 +        self.send_response(302)
    1.62 +        self.send_header("Location","/%s/" % (long(time.time()*1000)))
    1.63 +        self.end_headers()
    1.64 +
    1.65 +    def list_tests(self, id):
    1.66 +        """List all test cases."""
    1.67 +        
    1.68 +        try:
    1.69 +            os.stat("tests")
    1.70 +        except IOError:
    1.71 +            return self.send_error(500, "Tests were not found")
    1.72 +
    1.73 +        self.send_response(200)
    1.74 +        self.send_header('Content-Type', "text/plain")
    1.75 +        self.end_headers()
    1.76 +        return self.recurse_dir(id,"tests")
    1.77 +
    1.78 +    def recurse_dir(self, id, path):
    1.79 +        hasDir = 0
    1.80 +
    1.81 +        dir = os.listdir(path)
    1.82 +        dir.sort()
    1.83 +        
    1.84 +        for i in dir:
    1.85 +            if i == 'CVS':
    1.86 +              continue
    1.87 +            mode = os.stat(path+'/'+i)[ST_MODE]
    1.88 +            if S_ISDIR(mode):
    1.89 +                hasDir = 1
    1.90 +                self.recurse_dir(id,path+"/"+i)
    1.91 +            elif hasDir:
    1.92 +                print "%s: Warning! dir and non dir are mixed." % (path)
    1.93 +
    1.94 +        if not hasDir:
    1.95 +            self.wfile.write("http://localhost:8000/%s/%s/\n" % (id, path))
    1.96 +
    1.97 +    def copyfileobj(self, src, dst):
    1.98 +        """See shutil.copyfileobj from 2.x
    1.99 +
   1.100 +        I want this to be usable with 1.5 though"""
   1.101 +
   1.102 +        while 1:
   1.103 +            data = src.read(4096)
   1.104 +            if not data: break
   1.105 +            dst.write(data)
   1.106 +
   1.107 +    default_reply = "Testcase %s for %s loaded\n"
   1.108 +
   1.109 +    def handle_request(self, id, req):
   1.110 +        """Answer a request
   1.111 +
   1.112 +        We first look for a file with the name of the request.
   1.113 +        If that exists, then we spit that out, otherwise we
   1.114 +        open req.headers and req.body (if available) separately.
   1.115 +
   1.116 +        Why would you want to split it out?
   1.117 +        a) binary files
   1.118 +        b) Separating it out will send the 'standard' headers,
   1.119 +        and handle the Connection: details for you, if you're
   1.120 +        not testing that.
   1.121 +        c) You don't need to come up with your own body"""
   1.122 +
   1.123 +        res = results.results(id)
   1.124 +
   1.125 +        path = string.join(string.split(req, '/')[:-1], '/')
   1.126 +
   1.127 +        path = path + '/'
   1.128 +
   1.129 +        tester = res.get_tester(path)
   1.130 +
   1.131 +        self.fname = string.split(req,'/')[-1]
   1.132 +
   1.133 +        if not self.fname:
   1.134 +            self.fname = tester.baseName
   1.135 +
   1.136 +        if not tester.verify_request(self):
   1.137 +            res.set_tester(req, tester)
   1.138 +            return self.send_error(400, tester.reason)
   1.139 +
   1.140 +        ### perhaps this isn't the best design model...
   1.141 +        res.set_tester(path, tester)
   1.142 +
   1.143 +        del res
   1.144 +
   1.145 +        if req[-1:] == '/':
   1.146 +            req = req + tester.baseName
   1.147 +        
   1.148 +        try:
   1.149 +            f = open(req, 'rb')
   1.150 +            self.log_message('"%s" sent successfully for %s',
   1.151 +                             self.requestline,
   1.152 +                             id)
   1.153 +            self.copyfileobj(f,self.wfile)
   1.154 +            return f.close()
   1.155 +        except IOError:
   1.156 +            try:
   1.157 +                f = open(req+".headers", 'rb')
   1.158 +            except IOError:
   1.159 +                return self.send_error(404, "File %s not found" % (req))
   1.160 +        
   1.161 +        self.send_response(f.readline())
   1.162 +        # XXX - I should parse these out, and use send_header instead
   1.163 +        # so that I can change behaviour (like keep-alive...)
   1.164 +        # But then I couldn't test 'incorrect' header formats
   1.165 +        self.copyfileobj(f,self.wfile)
   1.166 +        f.close()
   1.167 +
   1.168 +        try:
   1.169 +            f = open(req+".body", 'rb')
   1.170 +            ## XXXXX - Need to configify this
   1.171 +            ## and then send content-length, etc
   1.172 +            self.end_headers()
   1.173 +            self.copyfileobj(f, self.wfile)
   1.174 +            return f.close()
   1.175 +        except IOError:
   1.176 +            self.send_header('Content-Type', "text/plain")
   1.177 +            body = self.default_reply % (req, id)
   1.178 +            self.send_header('Content-Length', len(body))
   1.179 +            self.end_headers()
   1.180 +            self.wfile.write(body)
   1.181 +
   1.182 +    def send_response(self, line, msg=None):
   1.183 +        if msg:
   1.184 +            return BaseHTTPServer.BaseHTTPRequestHandler.send_response(self, line,msg)
   1.185 +        try:
   1.186 +            x = int(line)
   1.187 +            BaseHTTPServer.BaseHTTPRequestHandler.send_response(self, x)
   1.188 +        except ValueError:
   1.189 +            tuple = string.split(line, ' ',2)
   1.190 +            ## XXXX
   1.191 +            old = self.protocol_version
   1.192 +            self.protocol_version = tuple[0]
   1.193 +            BaseHTTPServer.BaseHTTPRequestHandler.send_response(self, int(tuple[1]), tuple[2][:-1])
   1.194 +            self.protocol_version = old
   1.195 +
   1.196 +import socket
   1.197 +
   1.198 +# I need to thread this, with the mixin class
   1.199 +class RegressionHTTPServer(BaseHTTPServer.HTTPServer):
   1.200 +    # The 1.5.2 version doesn't do this:
   1.201 +    def server_bind(self):
   1.202 +        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
   1.203 +        BaseHTTPServer.HTTPServer.server_bind(self)
   1.204 +
   1.205 +def run(HandlerClass = RegressionHTTPRequestHandler,
   1.206 +        ServerClass = RegressionHTTPServer):
   1.207 +    BaseHTTPServer.test(HandlerClass, ServerClass)
   1.208 +
   1.209 +if __name__ == '__main__':
   1.210 +    run()

mercurial