summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozserve
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/mozbase/mozserve
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/mozbase/mozserve')
-rw-r--r--testing/mozbase/mozserve/mozserve/__init__.py12
-rw-r--r--testing/mozbase/mozserve/mozserve/servers.py289
-rw-r--r--testing/mozbase/mozserve/setup.py17
3 files changed, 318 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..9e884916a1
--- /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, Http2Server, Http3Server
+
+__all__ = ["Http3Server", "Http2Server", "DoHServer"]
diff --git a/testing/mozbase/mozserve/mozserve/servers.py b/testing/mozbase/mozserve/mozserve/servers.py
new file mode 100644
index 0000000000..fe75a12496
--- /dev/null
+++ b/testing/mozbase/mozserve/mozserve/servers.py
@@ -0,0 +1,289 @@
+# 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
+from threading import Thread
+
+
+@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 read_streams(self, name, proc, pipe):
+ output = "stdout" if pipe == proc.stdout else "stderr"
+ for line in iter(pipe.readline, ""):
+ self._log.info("server: %s [%s] %s" % (name, output, line))
+
+ 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)
+ name = "http3server"
+ t1 = Thread(
+ target=self.read_streams,
+ args=(name, process, process.stdout),
+ daemon=True,
+ )
+ t1.start()
+ t2 = Thread(
+ target=self.read_streams,
+ args=(name, process, process.stderr),
+ daemon=True,
+ )
+ t2.start()
+ 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)
+ else:
+ self._log.error("http3server failed to start?")
+ 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
+ self._http3ServerProc = {}
+
+
+class NodeHttp2Server(object):
+ """
+ Class which encapsulates a Node Http/2 server
+ """
+
+ def __init__(self, name, options, env, logger):
+ if isinstance(options, Namespace):
+ options = vars(options)
+ self._name = name
+ 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
+ self._searchStr = options["searchStr"]
+ self._alpn = options["alpn"]
+
+ def port(self):
+ return self._port
+
+ def start(self):
+ if not os.path.exists(self._serverPath):
+ raise Exception(
+ "%s server not found at %s" % (self._name, self._serverPath)
+ )
+
+ self._log.info(
+ "mozserve | Found %s server path: %s" % (self._name, 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),
+ "alpn={}".format(self._alpn),
+ ],
+ 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 | %s server msg: %s" % (self._name, msg))
+ if "server listening" in msg:
+ searchObj = re.search(self._searchStr, msg, 0)
+ if searchObj:
+ self._port = int(searchObj.group(1))
+ self._log.info(
+ "%s server started at port: %d" % (self._name, self._port)
+ )
+ except OSError as e:
+ # This occurs if the subprocess couldn't be started
+ self._log.error("Could not run %s server: %s" % (self._name, 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
+
+
+class DoHServer(object):
+ """
+ Class which encapsulates the DoH server
+ """
+
+ def __init__(self, options, env, logger):
+ options["searchStr"] = r"DoH server listening on ports ([0-9]+)"
+ self._server = NodeHttp2Server("DoH", options, env, logger)
+
+ def port(self):
+ return self._server.port()
+
+ def start(self):
+ self._server.start()
+
+ def stop(self):
+ self._server.stop()
+
+
+class Http2Server(object):
+ """
+ Class which encapsulates the Http2 server
+ """
+
+ def __init__(self, options, env, logger):
+ options["searchStr"] = r"Http2 server listening on ports ([0-9]+)"
+ options["dstServerPort"] = -1
+ options["alpn"] = ""
+ self._server = NodeHttp2Server("Http/2", options, env, logger)
+
+ def port(self):
+ return self._server.port()
+
+ def start(self):
+ self._server.start()
+
+ def stop(self):
+ self._server.stop()
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,
+)