Issue #18215: Add script Tools/ssl/test_multiple_versions.py to compile and
run Python's unit tests with multiple versions of OpenSSL.
This commit is contained in:
parent
7a2157fca7
commit
cb3558dcc1
|
@ -200,6 +200,9 @@ Tests
|
|||
Build
|
||||
-----
|
||||
|
||||
- Issue #18215: Add script Tools/ssl/test_multiple_versions.py to compile and
|
||||
run Python's unit tests with multiple versions of OpenSSL.
|
||||
|
||||
- Issue #19922: define _INCLUDE__STDC_A1_SOURCE in HP-UX to include mbstate_t
|
||||
for mbrtowc().
|
||||
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
#./python
|
||||
"""Run Python tests with multiple installations of OpenSSL
|
||||
|
||||
The script
|
||||
|
||||
(1) downloads OpenSSL tar bundle
|
||||
(2) extracts it to ../openssl/src/openssl-VERSION/
|
||||
(3) compiles OpenSSL
|
||||
(4) installs OpenSSL into ../openssl/VERSION/
|
||||
(5) forces a recompilation of Python modules using the
|
||||
header and library files from ../openssl/VERSION/
|
||||
(6) runs Python's test suite
|
||||
|
||||
The script must be run with Python's build directory as current working
|
||||
directory:
|
||||
|
||||
./python Tools/ssl/test_multiple_versions.py
|
||||
|
||||
The script uses LD_RUN_PATH, LD_LIBRARY_PATH, CPPFLAGS and LDFLAGS to bend
|
||||
search paths for header files and shared libraries. It's known to work on
|
||||
Linux with GCC 4.x.
|
||||
|
||||
(c) 2013 Christian Heimes <christian@python.org>
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import tarfile
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib.request import urlopen
|
||||
|
||||
log = logging.getLogger("multissl")
|
||||
|
||||
OPENSSL_VERSIONS = [
|
||||
"0.9.7m", "0.9.8i", "0.9.8l", "0.9.8m", "0.9.8y", "1.0.0k", "1.0.1e"
|
||||
]
|
||||
FULL_TESTS = [
|
||||
"test_asyncio", "test_ftplib", "test_hashlib", "test_httplib",
|
||||
"test_imaplib", "test_nntplib", "test_poplib", "test_smtplib",
|
||||
"test_smtpnet", "test_urllib2_localnet", "test_venv"
|
||||
]
|
||||
MINIMAL_TESTS = ["test_ssl", "test_hashlib"]
|
||||
CADEFAULT = True
|
||||
HERE = os.path.abspath(os.getcwd())
|
||||
DEST_DIR = os.path.abspath(os.path.join(HERE, os.pardir, "openssl"))
|
||||
|
||||
|
||||
class BuildSSL:
|
||||
url_template = "https://www.openssl.org/source/openssl-{}.tar.gz"
|
||||
|
||||
module_files = ["Modules/_ssl.c",
|
||||
"Modules/socketmodule.c",
|
||||
"Modules/_hashopenssl.c"]
|
||||
|
||||
def __init__(self, version, openssl_compile_args=(), destdir=DEST_DIR):
|
||||
self._check_python_builddir()
|
||||
self.version = version
|
||||
self.openssl_compile_args = openssl_compile_args
|
||||
# installation directory
|
||||
self.install_dir = os.path.join(destdir, version)
|
||||
# source file
|
||||
self.src_file = os.path.join(destdir, "src",
|
||||
"openssl-{}.tar.gz".format(version))
|
||||
# build directory (removed after install)
|
||||
self.build_dir = os.path.join(destdir, "src",
|
||||
"openssl-{}".format(version))
|
||||
|
||||
@property
|
||||
def openssl_cli(self):
|
||||
"""openssl CLI binary"""
|
||||
return os.path.join(self.install_dir, "bin", "openssl")
|
||||
|
||||
@property
|
||||
def openssl_version(self):
|
||||
"""output of 'bin/openssl version'"""
|
||||
env = os.environ.copy()
|
||||
env["LD_LIBRARY_PATH"] = self.lib_dir
|
||||
cmd = [self.openssl_cli, "version"]
|
||||
return self._subprocess_output(cmd, env=env)
|
||||
|
||||
@property
|
||||
def pyssl_version(self):
|
||||
"""Value of ssl.OPENSSL_VERSION"""
|
||||
env = os.environ.copy()
|
||||
env["LD_LIBRARY_PATH"] = self.lib_dir
|
||||
cmd = ["./python", "-c", "import ssl; print(ssl.OPENSSL_VERSION)"]
|
||||
return self._subprocess_output(cmd, env=env)
|
||||
|
||||
@property
|
||||
def include_dir(self):
|
||||
return os.path.join(self.install_dir, "include")
|
||||
|
||||
@property
|
||||
def lib_dir(self):
|
||||
return os.path.join(self.install_dir, "lib")
|
||||
|
||||
@property
|
||||
def has_openssl(self):
|
||||
return os.path.isfile(self.openssl_cli)
|
||||
|
||||
@property
|
||||
def has_src(self):
|
||||
return os.path.isfile(self.src_file)
|
||||
|
||||
def _subprocess_call(self, cmd, stdout=subprocess.DEVNULL, env=None,
|
||||
**kwargs):
|
||||
log.debug("Call '{}'".format(" ".join(cmd)))
|
||||
return subprocess.check_call(cmd, stdout=stdout, env=env, **kwargs)
|
||||
|
||||
def _subprocess_output(self, cmd, env=None, **kwargs):
|
||||
log.debug("Call '{}'".format(" ".join(cmd)))
|
||||
out = subprocess.check_output(cmd, env=env)
|
||||
return out.strip().decode("utf-8")
|
||||
|
||||
def _check_python_builddir(self):
|
||||
if not os.path.isfile("python") or not os.path.isfile("setup.py"):
|
||||
raise ValueError("Script must be run in Python build directory")
|
||||
|
||||
def _download_openssl(self):
|
||||
"""Download OpenSSL source dist"""
|
||||
src_dir = os.path.dirname(self.src_file)
|
||||
if not os.path.isdir(src_dir):
|
||||
os.makedirs(src_dir)
|
||||
url = self.url_template.format(self.version)
|
||||
log.info("Downloading OpenSSL from {}".format(url))
|
||||
req = urlopen(url, cadefault=CADEFAULT)
|
||||
# KISS, read all, write all
|
||||
data = req.read()
|
||||
log.info("Storing {}".format(self.src_file))
|
||||
with open(self.src_file, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
def _unpack_openssl(self):
|
||||
"""Unpack tar.gz bundle"""
|
||||
# cleanup
|
||||
if os.path.isdir(self.build_dir):
|
||||
shutil.rmtree(self.build_dir)
|
||||
os.makedirs(self.build_dir)
|
||||
|
||||
tf = tarfile.open(self.src_file)
|
||||
base = "openssl-{}/".format(self.version)
|
||||
# force extraction into build dir
|
||||
members = tf.getmembers()
|
||||
for member in members:
|
||||
if not member.name.startswith(base):
|
||||
raise ValueError(member.name)
|
||||
member.name = member.name[len(base):]
|
||||
log.info("Unpacking files to {}".format(self.build_dir))
|
||||
tf.extractall(self.build_dir, members)
|
||||
|
||||
def _build_openssl(self):
|
||||
"""Now build openssl"""
|
||||
log.info("Running build in {}".format(self.install_dir))
|
||||
cwd = self.build_dir
|
||||
cmd = ["./config", "shared", "--prefix={}".format(self.install_dir)]
|
||||
cmd.extend(self.openssl_compile_args)
|
||||
self._subprocess_call(cmd, cwd=cwd)
|
||||
self._subprocess_call(["make"], cwd=cwd)
|
||||
|
||||
def _install_openssl(self, remove=True):
|
||||
self._subprocess_call(["make", "install"], cwd=self.build_dir)
|
||||
if remove:
|
||||
shutil.rmtree(self.build_dir)
|
||||
|
||||
def install_openssl(self):
|
||||
if not self.has_openssl:
|
||||
if not self.has_src:
|
||||
self._download_openssl()
|
||||
else:
|
||||
log.debug("Already has src {}".format(self.src_file))
|
||||
self._unpack_openssl()
|
||||
self._build_openssl()
|
||||
self._install_openssl()
|
||||
else:
|
||||
log.info("Already has installation {}".format(self.install_dir))
|
||||
# validate installation
|
||||
version = self.openssl_version
|
||||
if self.version not in version:
|
||||
raise ValueError(version)
|
||||
|
||||
def touch_pymods(self):
|
||||
# force a rebuild of all modules that use OpenSSL APIs
|
||||
for fname in self.module_files:
|
||||
os.utime(fname)
|
||||
|
||||
def recompile_pymods(self):
|
||||
log.info("Using OpenSSL build from {}".format(self.build_dir))
|
||||
# overwrite header and library search paths
|
||||
env = os.environ.copy()
|
||||
env["CPPFLAGS"] = "-I{}".format(self.include_dir)
|
||||
env["LDFLAGS"] = "-L{}".format(self.lib_dir)
|
||||
# set rpath
|
||||
env["LD_RUN_PATH"] = self.lib_dir
|
||||
|
||||
log.info("Rebuilding Python modules")
|
||||
self.touch_pymods()
|
||||
cmd = ["./python", "setup.py", "build"]
|
||||
self._subprocess_call(cmd, env=env)
|
||||
|
||||
def check_pyssl(self):
|
||||
version = self.pyssl_version
|
||||
if self.version not in version:
|
||||
raise ValueError(version)
|
||||
|
||||
def run_pytests(self, *args):
|
||||
cmd = ["./python", "-m", "test"]
|
||||
cmd.extend(args)
|
||||
self._subprocess_call(cmd, stdout=None)
|
||||
|
||||
def run_python_tests(self, *args):
|
||||
self.recompile_pymods()
|
||||
self.check_pyssl()
|
||||
self.run_pytests(*args)
|
||||
|
||||
|
||||
def main(*args):
|
||||
builders = []
|
||||
for version in OPENSSL_VERSIONS:
|
||||
if version in ("0.9.8i", "0.9.8l"):
|
||||
openssl_compile_args = ("no-asm",)
|
||||
else:
|
||||
openssl_compile_args = ()
|
||||
builder = BuildSSL(version, openssl_compile_args)
|
||||
builder.install_openssl()
|
||||
builders.append(builder)
|
||||
|
||||
for builder in builders:
|
||||
builder.run_python_tests(*args)
|
||||
# final touch
|
||||
builder.touch_pymods()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format="*** %(levelname)s %(message)s")
|
||||
args = sys.argv[1:]
|
||||
if not args:
|
||||
args = ["-unetwork", "-v"]
|
||||
args.extend(FULL_TESTS)
|
||||
main(*args)
|
Loading…
Reference in New Issue