summaryrefslogtreecommitdiffstats
path: root/native_client_sdk
diff options
context:
space:
mode:
Diffstat (limited to 'native_client_sdk')
-rwxr-xr-x[-rw-r--r--]native_client_sdk/src/tools/httpd.py120
-rwxr-xr-xnative_client_sdk/src/tools/run.py21
-rwxr-xr-xnative_client_sdk/src/tools/tests/test_httpd.py108
3 files changed, 211 insertions, 38 deletions
diff --git a/native_client_sdk/src/tools/httpd.py b/native_client_sdk/src/tools/httpd.py
index 1fbdbe5..0e2ef67 100644..100755
--- a/native_client_sdk/src/tools/httpd.py
+++ b/native_client_sdk/src/tools/httpd.py
@@ -1,7 +1,9 @@
+#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import BaseHTTPServer
import imp
import logging
import multiprocessing
@@ -9,16 +11,14 @@ import optparse
import os
import SimpleHTTPServer # pylint: disable=W0611
import sys
+import time
+import urlparse
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
NACL_SDK_ROOT = os.path.dirname(SCRIPT_DIR)
-serve_dir = None
-delegate_map = {}
-
-
# We only run from the examples directory so that not too much is exposed
# via this HTTP server. Everything in the directory is served, so there should
# never be anything potentially sensitive in the serving directory, especially
@@ -37,14 +37,27 @@ def SanityCheckDirectory(dirname):
sys.exit(1)
+class PluggableHTTPServer(BaseHTTPServer.HTTPServer):
+ def __init__(self, *args, **kwargs):
+ BaseHTTPServer.HTTPServer.__init__(self, *args)
+ self.serve_dir = kwargs.get('serve_dir', '.')
+ self.delegate_map = {}
+ self.running = True
+ self.result = 0
+
+ def Shutdown(self, result=0):
+ self.running = False
+ self.result = result
+
+
class PluggableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def _FindDelegateAtPath(self, dirname):
# First check the cache...
logging.debug('Looking for cached delegate in %s...' % dirname)
handler_script = os.path.join(dirname, 'handler.py')
- if dirname in delegate_map:
- result = delegate_map[dirname]
+ if dirname in self.server.delegate_map:
+ result = self.server.delegate_map[dirname]
if result is None:
logging.debug('Found None.')
else:
@@ -79,7 +92,7 @@ class PluggableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
delegate = self._FindDelegateForURLRecurse(parent_dir, abs_root)
logging.debug('Adding delegate to cache for %s.' % cur_dir)
- delegate_map[cur_dir] = delegate
+ self.server.delegate_map[cur_dir] = delegate
return delegate
def _FindDelegateForURL(self, url_path):
@@ -89,7 +102,7 @@ class PluggableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
else:
dirname = os.path.dirname(path)
- abs_serve_dir = os.path.abspath(serve_dir)
+ abs_serve_dir = os.path.abspath(self.server.serve_dir)
delegate = self._FindDelegateForURLRecurse(dirname, abs_serve_dir)
if not delegate:
logging.info('No handler found for path %s. Using default.' % url_path)
@@ -105,6 +118,19 @@ class PluggableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
def do_GET(self):
+ # TODO(binji): pyauto tests use the ?quit=1 method to kill the server.
+ # Remove this when we kill the pyauto tests.
+ _, _, _, query, _ = urlparse.urlsplit(self.path)
+ if query:
+ params = urlparse.parse_qs(query)
+ if '1' in params.get('quit', None):
+ self.send_response(200, 'OK')
+ self.send_header('Content-type', 'text/html')
+ self.send_header('Content-length', '0')
+ self.end_headers()
+ self.server.Shutdown()
+ return
+
delegate = self._FindDelegateForURL(self.path)
if delegate:
return delegate.do_GET(self)
@@ -127,12 +153,12 @@ class LocalHTTPServer(object):
"""Class to start a local HTTP server as a child process."""
def __init__(self, dirname, port):
- global serve_dir
- serve_dir = dirname
parent_conn, child_conn = multiprocessing.Pipe()
self.process = multiprocessing.Process(
target=_HTTPServerProcess,
- args=(child_conn, serve_dir, port))
+ args=(child_conn, dirname, port, {
+ 'serve_dir': dirname,
+ }))
self.process.start()
if parent_conn.poll(10): # wait 10 seconds
self.port = parent_conn.recv()
@@ -141,6 +167,47 @@ class LocalHTTPServer(object):
self.conn = parent_conn
+ def ServeForever(self):
+ """Serve until the child HTTP process tells us to stop.
+
+ Returns:
+ The result from the child (as an errorcode), or 0 if the server was
+ killed not by the child (by KeyboardInterrupt for example).
+ """
+ child_result = 0
+ try:
+ # Block on this pipe, waiting for a response from the child process.
+ child_result = self.conn.recv()
+ except KeyboardInterrupt:
+ pass
+ finally:
+ self.Shutdown()
+ return child_result
+
+ def ServeUntilSubprocessDies(self, process):
+ """Serve until the child HTTP process tells us to stop or |subprocess| dies.
+
+ Returns:
+ The result from the child (as an errorcode), or 0 if |subprocess| died,
+ or the server was killed some other way (by KeyboardInterrupt for
+ example).
+ """
+ child_result = 0
+ try:
+ while True:
+ if process.poll() is not None:
+ child_result = 0
+ break
+ if self.conn.poll():
+ child_result = self.conn.recv()
+ break
+ time.sleep(0)
+ except KeyboardInterrupt:
+ pass
+ finally:
+ self.Shutdown()
+ return child_result
+
def Shutdown(self):
"""Send a message to the child HTTP server process and wait for it to
finish."""
@@ -157,7 +224,7 @@ class LocalHTTPServer(object):
return 'http://localhost:%d/%s' % (self.port, rel_url)
-def _HTTPServerProcess(conn, dirname, port):
+def _HTTPServerProcess(conn, dirname, port, server_kwargs):
"""Run a local httpserver with the given port or an ephemeral port.
This function assumes it is run as a child process using multiprocessing.
@@ -165,29 +232,30 @@ def _HTTPServerProcess(conn, dirname, port):
Args:
conn: A connection to the parent process. The child process sends
the local port, and waits for a message from the parent to
- stop serving.
+ stop serving. It also sends a "result" back to the parent -- this can
+ be used to allow a client-side test to notify the server of results.
dirname: The directory to serve. All files are accessible through
http://localhost:<port>/path/to/filename.
port: The port to serve on. If 0, an ephemeral port will be chosen.
+ server_kwargs: A dict that will be passed as kwargs to the server.
"""
- import BaseHTTPServer
-
try:
os.chdir(dirname)
- httpd = BaseHTTPServer.HTTPServer(('', port), PluggableHTTPRequestHandler)
+ httpd = PluggableHTTPServer(('', port), PluggableHTTPRequestHandler,
+ **server_kwargs)
conn.send(httpd.server_address[1]) # the chosen port number
httpd.timeout = 0.5 # seconds
- running = True
- while running:
+ while httpd.running:
# Flush output for MSVS Add-In.
sys.stdout.flush()
sys.stderr.flush()
httpd.handle_request()
if conn.poll():
- running = conn.recv()
+ httpd.running = conn.recv()
except KeyboardInterrupt:
pass
finally:
+ conn.send(httpd.result)
conn.close()
@@ -206,18 +274,12 @@ def main(args):
if options.do_safe_check:
SanityCheckDirectory(options.serve_dir)
- server = LocalHTTPServer(options.serve_dir, options.port)
+ server = LocalHTTPServer(options.serve_dir, int(options.port))
- # Serve forever.
+ # Serve until the client tells us to stop. When it does, it will give us an
+ # errorcode.
print 'Serving %s on %s...' % (options.serve_dir, server.GetURL(''))
- try:
- while True:
- pass
- except KeyboardInterrupt:
- pass
- finally:
- print 'Stopping server.'
- server.Shutdown()
+ return server.ServeForever()
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
diff --git a/native_client_sdk/src/tools/run.py b/native_client_sdk/src/tools/run.py
index 7c4ddf2..3fbd0e0 100755
--- a/native_client_sdk/src/tools/run.py
+++ b/native_client_sdk/src/tools/run.py
@@ -37,18 +37,21 @@ def main(args):
# 0 means use an ephemeral port.
server = httpd.LocalHTTPServer(options.serve_dir, 0)
+ print 'Serving %s on %s...' % (options.serve_dir, server.GetURL(''))
+ env = copy.copy(os.environ)
+ for e in options.environ:
+ key, value = map(str.strip, e.split('='))
+ env[key] = value
+
+ cmd = args + [server.GetURL(options.path)]
+ print 'Running: %s...' % (' '.join(cmd),)
+ process = subprocess.Popen(cmd, env=env)
try:
- env = copy.copy(os.environ)
- for e in options.environ:
- key, value = map(str.strip, e.split('='))
- env[key] = value
-
- cmd = args + [server.GetURL(options.path)]
- print 'Running: %s...' % (' '.join(cmd),)
- subprocess.call(cmd, env=env)
+ return server.ServeUntilSubprocessDies(process)
finally:
- server.Shutdown()
+ if process.returncode is None:
+ process.kill()
if __name__ == '__main__':
diff --git a/native_client_sdk/src/tools/tests/test_httpd.py b/native_client_sdk/src/tools/tests/test_httpd.py
new file mode 100755
index 0000000..83390d1
--- /dev/null
+++ b/native_client_sdk/src/tools/tests/test_httpd.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import re
+import sys
+import subprocess
+import tempfile
+import unittest
+import urllib2
+
+SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
+PARENT_DIR = os.path.dirname(SCRIPT_DIR)
+
+sys.path.append(PARENT_DIR)
+
+import httpd
+
+
+class HTTPDTest(unittest.TestCase):
+ def setUp(self):
+ self.server = httpd.LocalHTTPServer('.', 0)
+
+ def tearDown(self):
+ self.server.Shutdown()
+
+ def testQuit(self):
+ urllib2.urlopen(self.server.GetURL('?quit=1'))
+ self.server.process.join(10) # Wait 10 seconds for the process to finish.
+ self.assertFalse(self.server.process.is_alive())
+
+
+class RunTest(unittest.TestCase):
+ def setUp(self):
+ self.process = None
+ self.tempscript = None
+
+ def tearDown(self):
+ if self.process and self.process.returncode is None:
+ self.process.kill()
+ if self.tempscript:
+ os.remove(self.tempscript)
+
+ def _Run(self, args=None):
+ args = args or []
+ run_py = os.path.join(PARENT_DIR, 'run.py')
+ cmd = [sys.executable, run_py]
+ cmd.extend(args)
+ self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = self.process.communicate()
+ try:
+ self.assertEqual(0, self.process.returncode)
+ except AssertionError:
+ print 'subprocess failed: %s:\nstdout: %s\nstderr:%s\n' % (
+ ' '.join(cmd), stdout, stderr)
+ return stdout
+
+ def _WriteTempScript(self, script):
+ fd, filename = tempfile.mkstemp(suffix='.py')
+ lines = script.splitlines()
+ assert len(lines[0]) == 0 # First line is always empty.
+ # Count the number of spaces after the first newline.
+ m = re.match(r'\s*', lines[1])
+ assert m
+ indent = len(m.group(0))
+ script = '\n'.join(line[indent:] for line in lines[1:])
+
+ os.write(fd, script)
+ os.close(fd)
+ self.tempscript = filename
+
+ def testQuit(self):
+ self._WriteTempScript(r"""
+ import sys
+ import time
+ import urllib2
+
+ print 'running tempscript'
+ sys.stdout.flush()
+ f = urllib2.urlopen(sys.argv[-1] + '?quit=1')
+ f.read()
+ f.close()
+ time.sleep(10)
+
+ # Should be killed before this prints.
+ print 'Not killed yet.'
+ sys.stdout.flush()
+ sys.exit(0)
+ """)
+ stdout = self._Run(['--', sys.executable, self.tempscript])
+ self.assertTrue('running tempscript' in stdout)
+ self.assertTrue("Not killed yet" not in stdout)
+
+ def testSubprocessDies(self):
+ self._WriteTempScript(r"""
+ import sys
+ print 'running tempscript'
+ sys.stdout.flush()
+ sys.exit(1)
+ """)
+ stdout = self._Run(['--', sys.executable, self.tempscript])
+ self.assertTrue('running tempscript' in stdout)
+
+if __name__ == '__main__':
+ unittest.main()