summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozserve
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozbase/mozserve')
-rw-r--r--testing/mozbase/mozserve/mozserve/__init__.py12
-rw-r--r--testing/mozbase/mozserve/mozserve/servers.py229
-rw-r--r--testing/mozbase/mozserve/setup.py17
3 files changed, 258 insertions, 0 deletions
diff --git a/testing/mozbase/mozserve/mozserve/__init__.py b/testing/mozbase/mozserve/mozserve/__init__.py
new file mode 100644
index 0000000000..2354857354
--- /dev/null
+++ b/testing/mozbase/mozserve/mozserve/__init__.py
@@ -0,0 +1,12 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+mozserve is a simple script that is used to launch test servers, and
+is designed for use in mochitest and xpcshelltest.
+"""
+
+from .servers import DoHServer, Http3Server
+
+__all__ = ["Http3Server", "DoHServer"]
diff --git a/testing/mozbase/mozserve/mozserve/servers.py b/testing/mozbase/mozserve/mozserve/servers.py
new file mode 100644
index 0000000000..da7075601f
--- /dev/null
+++ b/testing/mozbase/mozserve/mozserve/servers.py
@@ -0,0 +1,229 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import copy
+import os
+import re
+import subprocess
+import sys
+import time
+from argparse import Namespace
+from contextlib import contextmanager
+from subprocess import PIPE, Popen
+
+
+@contextmanager
+def popenCleanupHack(isWin):
+ """
+ Hack to work around https://bugs.python.org/issue37380
+ The basic idea is that on old versions of Python on Windows,
+ we need to clear subprocess._cleanup before we call Popen(),
+ then restore it afterwards.
+ """
+ savedCleanup = None
+ if isWin and sys.version_info[0] == 3 and sys.version_info < (3, 7, 5):
+ savedCleanup = subprocess._cleanup
+ subprocess._cleanup = lambda: None
+ try:
+ yield
+ finally:
+ if savedCleanup:
+ subprocess._cleanup = savedCleanup
+
+
+class Http3Server(object):
+ """
+ Class which encapsulates the Http3 server
+ """
+
+ def __init__(self, options, env, logger):
+ if isinstance(options, Namespace):
+ options = vars(options)
+ self._log = logger
+ self._profileDir = options["profilePath"]
+ self._env = copy.deepcopy(env)
+ self._ports = {}
+ self._echConfig = ""
+ self._isMochitest = options["isMochitest"]
+ self._http3ServerPath = options["http3ServerPath"]
+ self._isWin = options["isWin"]
+ self._http3ServerProc = {}
+ self._proxyPort = -1
+ if options.get("proxyPort"):
+ self._proxyPort = options["proxyPort"]
+
+ def ports(self):
+ return self._ports
+
+ def echConfig(self):
+ return self._echConfig
+
+ def start(self):
+ if not os.path.exists(self._http3ServerPath):
+ raise Exception("Http3 server not found at %s" % self._http3ServerPath)
+
+ self._log.info("mozserve | Found Http3Server path: %s" % self._http3ServerPath)
+
+ dbPath = os.path.join(self._profileDir, "cert9.db")
+ if not os.path.exists(dbPath):
+ raise Exception("cert db not found at %s" % dbPath)
+
+ dbPath = self._profileDir
+ self._log.info("mozserve | cert db path: %s" % dbPath)
+
+ try:
+ if self._isMochitest:
+ self._env["MOZ_HTTP3_MOCHITEST"] = "1"
+ if self._proxyPort != -1:
+ self._env["MOZ_HTTP3_PROXY_PORT"] = str(self._proxyPort)
+ with popenCleanupHack(self._isWin):
+ process = Popen(
+ [self._http3ServerPath, dbPath],
+ stdin=PIPE,
+ stdout=PIPE,
+ stderr=PIPE,
+ env=self._env,
+ cwd=os.getcwd(),
+ universal_newlines=True,
+ )
+ self._http3ServerProc["http3Server"] = process
+
+ # Check to make sure the server starts properly by waiting for it to
+ # tell us it's started
+ msg = process.stdout.readline()
+ self._log.info("mozserve | http3 server msg: %s" % msg)
+ if "server listening" in msg:
+ searchObj = re.search(
+ r"HTTP3 server listening on ports ([0-9]+), ([0-9]+), ([0-9]+), ([0-9]+) and ([0-9]+)."
+ " EchConfig is @([\x00-\x7F]+)@",
+ msg,
+ 0,
+ )
+ if searchObj:
+ self._ports["MOZHTTP3_PORT"] = searchObj.group(1)
+ self._ports["MOZHTTP3_PORT_FAILED"] = searchObj.group(2)
+ self._ports["MOZHTTP3_PORT_ECH"] = searchObj.group(3)
+ self._ports["MOZHTTP3_PORT_PROXY"] = searchObj.group(4)
+ self._ports["MOZHTTP3_PORT_NO_RESPONSE"] = searchObj.group(5)
+ self._echConfig = searchObj.group(6)
+ except OSError as e:
+ # This occurs if the subprocess couldn't be started
+ self._log.error("Could not run the http3 server: %s" % (str(e)))
+
+ def stop(self):
+ """
+ Shutdown our http3Server process, if it exists
+ """
+ for name, proc in self._http3ServerProc.items():
+ self._log.info("%s server shutting down ..." % name)
+ if proc.poll() is not None:
+ self._log.info("Http3 server %s already dead %s" % (name, proc.poll()))
+ else:
+ proc.terminate()
+ retries = 0
+ while proc.poll() is None:
+ time.sleep(0.1)
+ retries += 1
+ if retries > 40:
+ self._log.info("Killing proc")
+ proc.kill()
+ break
+
+ def dumpOutput(fd, label):
+ firstTime = True
+ for msg in fd:
+ if firstTime:
+ firstTime = False
+ self._log.info("Process %s" % label)
+ self._log.info(msg)
+
+ dumpOutput(proc.stdout, "stdout")
+ dumpOutput(proc.stderr, "stderr")
+ self._http3ServerProc = {}
+
+
+class DoHServer(object):
+ """
+ Class which encapsulates the DoH server
+ """
+
+ def __init__(self, options, env, logger):
+ if isinstance(options, Namespace):
+ options = vars(options)
+ self._log = logger
+ self._port = options["port"]
+ self._env = copy.deepcopy(env)
+ self._nodeBin = options["nodeBin"]
+ self._serverPath = options["serverPath"]
+ self._dstServerPort = options["dstServerPort"]
+ self._isWin = options["isWin"]
+ self._nodeProc = None
+
+ def port(self):
+ return self._port
+
+ def start(self):
+ if not os.path.exists(self._serverPath):
+ raise Exception("DoH server not found at %s" % self._serverPath)
+
+ self._log.info("mozserve | Found DoH server path: %s" % self._serverPath)
+
+ if not os.path.exists(self._nodeBin) or not os.path.isfile(self._nodeBin):
+ raise Exception("node not found at path %s" % (self._nodeBin))
+
+ self._log.info("Found node at %s" % (self._nodeBin))
+
+ try:
+ # We pipe stdin to node because the server will exit when its
+ # stdin reaches EOF
+ with popenCleanupHack(self._isWin):
+ process = Popen(
+ [
+ self._nodeBin,
+ self._serverPath,
+ "serverPort={}".format(self._dstServerPort),
+ "listeningPort={}".format(self._port),
+ ],
+ stdin=PIPE,
+ stdout=PIPE,
+ stderr=PIPE,
+ env=self._env,
+ cwd=os.getcwd(),
+ universal_newlines=True,
+ )
+ self._nodeProc = process
+
+ msg = process.stdout.readline()
+ self._log.info("runtests.py | DoH server msg: %s" % msg)
+ if "server listening" in msg:
+ searchObj = re.search(r"DoH server listening on ports ([0-9]+)", msg, 0)
+ if searchObj:
+ self._port = int(searchObj.group(1))
+ self._log.info("DoH server started at port: %d" % (self._port))
+ except OSError as e:
+ # This occurs if the subprocess couldn't be started
+ self._log.error("Could not run DoH server: %s" % (str(e)))
+
+ def stop(self):
+ """
+ Shut down our node process, if it exists
+ """
+ if self._nodeProc is not None:
+ if self._nodeProc.poll() is not None:
+ self._log.info("Node server already dead %s" % (self._nodeProc.poll()))
+ else:
+ self._nodeProc.terminate()
+
+ def dumpOutput(fd, label):
+ firstTime = True
+ for msg in fd:
+ if firstTime:
+ firstTime = False
+ self._log.info("Process %s" % label)
+ self._log.info(msg)
+
+ dumpOutput(self._nodeProc.stdout, "stdout")
+ dumpOutput(self._nodeProc.stderr, "stderr")
+
+ self._nodeProc = None
diff --git a/testing/mozbase/mozserve/setup.py b/testing/mozbase/mozserve/setup.py
new file mode 100644
index 0000000000..c00000bb0c
--- /dev/null
+++ b/testing/mozbase/mozserve/setup.py
@@ -0,0 +1,17 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from setuptools import setup
+
+PACKAGE_VERSION = "0.1"
+
+setup(
+ name="mozserve",
+ version=PACKAGE_VERSION,
+ description="Python test server launcher intended for use with Mozilla testing",
+ long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
+ license="MPL",
+ packages=["mozserve"],
+ zip_safe=False,
+)