[Python-checkins] cpython: Issue #18215: Add script Tools/ssl/test_multiple_versions.py to compile and

christian.heimes python-checkins at python.org
Mon Dec 16 14:36:09 CET 2013


http://hg.python.org/cpython/rev/7719efb182e3
changeset:   87996:7719efb182e3
parent:      87976:1f3f4147c35e
user:        Christian Heimes <christian at cheimes.de>
date:        Mon Dec 16 14:35:39 2013 +0100
summary:
  Issue #18215: Add script Tools/ssl/test_multiple_versions.py to compile and
run Python's unit tests with multiple versions of OpenSSL.

files:
  Misc/NEWS                           |    3 +
  Tools/ssl/test_multiple_versions.py |  241 ++++++++++++++++
  2 files changed, 244 insertions(+), 0 deletions(-)


diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -200,6 +200,9 @@
 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().
 
diff --git a/Tools/ssl/test_multiple_versions.py b/Tools/ssl/test_multiple_versions.py
new file mode 100644
--- /dev/null
+++ b/Tools/ssl/test_multiple_versions.py
@@ -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 at 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)

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list