Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-Twisted for openSUSE:Factory checked in at 2026-11-01 22:21:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Twisted (Old) and /work/SRC/openSUSE:Factory/.python-Twisted.new.1255 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Twisted" Sun Nov 1 22:21:11 2026 rev:82 rq:1393922 version:26.10.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-Twisted/python-Twisted.changes 2026-10-14 18:34:41.694793324 +0200 +++ /work/SRC/openSUSE:Factory/.python-Twisted.new.1255/python-Twisted.changes 2026-11-01 22:21:15.827641372 +0100 @@ -1,0 +2,31 @@ +Fri Oct 30 12:47:48 UTC 2026 - Markéta Machová <[email protected]> + +- Update to 26.10.0 (CVE-2026-83813, bsc#1287833, + CVE-2026-115778, bsc#1287678, CVE-2026-115775, bsc#1287675, + CVE-2026-115877, bsc#1287848, CVE-2026-115774, bsc#1287671, + CVE-2026-115776, bsc#1287676) + * It is now possible to set the SO_REUSEPORT and SO_REUSEADDR TCP + socket flags from user code. + * Python 3.14 is now supported. + * twisted.internet.defer.Deferred.__iter__, used when awaiting a + Deferred, is now about 2-3% faster. + * twisted.internet.tcp.Connection no longer supports returning a + non-None value from a protocol's dataReceived method + * Implementations of twisted.internet.interfaces.IProcessProtocol + are now required to provide a processExited method + * twisted.internet.tcp.Port.startListening() now closes the socket + when raising CannotListenError + * twisted.logger.LoggingFile.fileno now raises io.UnsupportedOperation + instead of returning -1. This fixes an interoperability issue with + multiprocessing. + * twistd now exits with a non-zero status given an unknown subcommand. + * twisted.protocols.ftp.FTPClient now uses ip address of the original + ftp server to open passive data connections by default, instead of + trusting PASV responses. + * _makeGetterFactory method in twisted.web.client has been removed. + * twisted.trial.runner.samefile is deprecated and replaced by + os.path.samefile. +- Drop merged pycryptography-use-csr.patch and CVE-2026-106454.patch +- Add upstream py315.patch to fix tests with python 3.15 + +------------------------------------------------------------------- Old: ---- CVE-2026-106454.patch pycryptography-use-csr.patch twisted-26.4.0.tar.gz New: ---- py315.patch twisted-26.10.0.tar.gz ----------(Old B)---------- Old: os.path.samefile. - Drop merged pycryptography-use-csr.patch and CVE-2026-106454.patch - Add upstream py315.patch to fix tests with python 3.15 Old: os.path.samefile. - Drop merged pycryptography-use-csr.patch and CVE-2026-106454.patch - Add upstream py315.patch to fix tests with python 3.15 ----------(Old E)---------- ----------(New B)---------- New:- Drop merged pycryptography-use-csr.patch and CVE-2026-106454.patch - Add upstream py315.patch to fix tests with python 3.15 ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Twisted.spec ++++++ --- /var/tmp/diff_new_pack.V7sxHF/_old 2026-11-01 22:21:17.219698983 +0100 +++ /var/tmp/diff_new_pack.V7sxHF/_new 2026-11-01 22:21:17.221699066 +0100 @@ -31,7 +31,7 @@ %endif %{?sle15_python_module_pythons} Name: python-Twisted%{psuffix} -Version: 26.4.0 +Version: 26.10.0 Release: 0 Summary: An asynchronous networking framework written in Python License: MIT @@ -46,10 +46,8 @@ Patch2: no-cython_test_exception_raiser.patch # PATCH-FIX-OPENSUSE remove-dependency-version-upper-bounds.patch boo#1190036 -- run with h2 >= 4.0.0 and priority >= 2.0 Patch3: remove-dependency-version-upper-bounds.patch -# PATCH-FIX-UPSTREAM: https://github.com/twisted/twisted/pull/12661 -Patch4: pycryptography-use-csr.patch -# PATCH-FIX-UPSTREAM CVE-2026-106454.patch bsc#1284420 gh#twisted/twisted@f570190 -Patch5: CVE-2026-106454.patch +# PATCH-FIX-UPSTREAM py315.patch https://github.com/twisted/twisted/pull/12602 Fix Python 3.15 compatibility issues in trial - #12602 +Patch4: py315.patch BuildRequires: %{python_module base >= 3.9} BuildRequires: %{python_module hatch-fancy-pypi-readme} BuildRequires: %{python_module hatchling} @@ -62,13 +60,13 @@ Requires: python-Twisted-tls = %{version} BuildArch: noarch # SECTION install requires -Requires: python-Automat >= 24.8.0 +Requires: python-Automat >= 24.8.1 Requires: python-attrs >= 22.2 Requires: python-constantly >= 15.1 -Requires: python-hyperlink >= 17.1.1 +Requires: python-hyperlink >= 18.0.0 Requires: python-incremental >= 24.7.0 -Requires: python-typing_extensions >= 4.2 -Requires: python-zope.interface >= 5 +Requires: python-typing_extensions >= 4.13.2 +Requires: python-zope.interface >= 8 # /SECTION %if %{with libalternatives} BuildRequires: alts @@ -84,6 +82,8 @@ BuildRequires: %{python_module hypothesis} # declared nowhere but required to pass 8 tests with timezone checks BuildRequires: %{python_module pytz} +# needs the ssh command for conch tests +BuildRequires: openssh %endif %python_subpackages @@ -105,9 +105,9 @@ %package tls Summary: TLS support for Twisted Requires: python-Twisted = %{version} -Requires: python-idna >= 2.4 -Requires: python-pyOpenSSL >= 25.2 -Requires: python-service_identity >= 18.1.0 +Requires: python-idna >= 2.5 +Requires: python-pyOpenSSL >= 25.3 +Requires: python-service_identity >= 21.1.0 %description tls Twisted is an extensible framework for Python programming, with special focus @@ -120,7 +120,7 @@ Requires: python-Twisted = %{version} Requires: python-appdirs >= 1.4.0 Requires: python-bcrypt >= 3.2.1 -Requires: python-cryptography >= 38 +Requires: python-cryptography >= 46 %description conch Twisted is an extensible framework for Python programming, with special focus ++++++ no-cython_test_exception_raiser.patch ++++++ --- /var/tmp/diff_new_pack.V7sxHF/_old 2026-11-01 22:21:17.272701177 +0100 +++ /var/tmp/diff_new_pack.V7sxHF/_new 2026-11-01 22:21:17.275701301 +0100 @@ -1,9 +1,9 @@ -Index: twisted-26.4.0/src/twisted/test/test_failure.py +Index: twisted-26.10.0/src/twisted/test/test_failure.py =================================================================== ---- twisted-26.4.0.orig/src/twisted/test/test_failure.py -+++ twisted-26.4.0/src/twisted/test/test_failure.py +--- twisted-26.10.0.orig/src/twisted/test/test_failure.py ++++ twisted-26.10.0/src/twisted/test/test_failure.py @@ -20,7 +20,8 @@ from types import TracebackType - from typing import Any, cast + from typing import Any from unittest import skipIf -from cython_test_exception_raiser import raiser ++++++ pycryptography-use-csr.patch -> py315.patch ++++++ --- /work/SRC/openSUSE:Factory/python-Twisted/pycryptography-use-csr.patch 2026-06-16 18:29:24.473542490 +0200 +++ /work/SRC/openSUSE:Factory/.python-Twisted.new.1255/py315.patch 2026-11-01 22:21:15.585631356 +0100 @@ -1,456 +1,114 @@ -From 5b4601c9965ffc92d6aa952b8c05127d5ac37307 Mon Sep 17 00:00:00 2001 -From: Alex Gaynor <[email protected]> -Date: Mon, 8 Jun 2026 08:51:19 -0700 -Subject: [PATCH 1/5] #12660 Replace OpenSSL.crypto.X509Req with - pyca/cryptography CSR - -Replace usage of pyOpenSSL's X509Req with pyca/cryptography's -CertificateSigningRequest in twisted.internet._sslverify and the -test_ssl certificate generation helpers. - -Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> +From 3962d15a2c593d2e47d240ab6f0c5352caca3464 Mon Sep 17 00:00:00 2001 +From: Lumir Balhar <[email protected]> +Date: Wed, 1 Apr 2026 12:56:27 +0200 +Subject: [PATCH] Fix Python 3.15 compatibility issues + +- Replace deprecated SourceFileLoader.load_module() with exec_module() +- Add addSubTest() method to _AdaptedReporter for Python 3.15 doctest support +- Update test expectations for Python 3.15 doctest changes: + - Different success/failure counts + - Changed error output format --- - src/twisted/internet/_sslverify.py | 103 ++++++++++++++++++++++----- - src/twisted/newsfragments/12660.misc | 0 - src/twisted/test/test_ssl.py | 42 +++++++---- - 3 files changed, 114 insertions(+), 31 deletions(-) - create mode 100644 src/twisted/newsfragments/12660.misc - -diff --git a/src/twisted/internet/_sslverify.py b/src/twisted/internet/_sslverify.py -index c6dabf6c4c4..f1cec9c2d87 100644 ---- a/src/twisted/internet/_sslverify.py -+++ b/src/twisted/internet/_sslverify.py -@@ -20,6 +20,9 @@ - - import attr - from constantly import FlagConstant, Flags, NamedConstant, Names -+from cryptography import x509 -+from cryptography.hazmat.primitives import hashes, serialization -+from cryptography.x509.oid import NameOID - from incremental import Version - - from twisted.internet.abstract import isIPAddress, isIPv6Address -@@ -169,6 +172,29 @@ def protocolNegotiationMechanisms() -> FlagConstant: - "emailAddress": "emailAddress", - } - -+_x509NameOIDs = { -+ "commonName": NameOID.COMMON_NAME, -+ "organizationName": NameOID.ORGANIZATION_NAME, -+ "organizationalUnitName": NameOID.ORGANIZATIONAL_UNIT_NAME, -+ "localityName": NameOID.LOCALITY_NAME, -+ "stateOrProvinceName": NameOID.STATE_OR_PROVINCE_NAME, -+ "countryName": NameOID.COUNTRY_NAME, -+ "emailAddress": NameOID.EMAIL_ADDRESS, -+} -+ -+# Reverse of _x509NameOIDs, for translating a parsed subject back into a -+# DistinguishedName. -+_x509OIDNames = {oid: name for name, oid in _x509NameOIDs.items()} -+ -+_digestAlgorithms = { -+ "md5": hashes.MD5, -+ "sha1": hashes.SHA1, -+ "sha224": hashes.SHA224, -+ "sha256": hashes.SHA256, -+ "sha384": hashes.SHA384, -+ "sha512": hashes.SHA512, -+} -+ - - class DistinguishedName(dict[str, bytes]): - """ -@@ -490,19 +516,51 @@ class CertificateRequest(CertBase): - - Certificate requests are given to certificate authorities to be signed and - returned resulting in an actual certificate. -+ -+ @ivar original: The underlying CSR object. -+ @type original: L{cryptography.x509.CertificateSigningRequest} - """ + src/twisted/trial/reporter.py | 13 +++++++++++++ + src/twisted/trial/runner.py | 6 +++++- + src/twisted/trial/test/test_doctest.py | 9 +++++++-- + src/twisted/trial/test/test_reporter.py | 17 ++++++++++++----- + 4 files changed, 37 insertions(+), 8 deletions(-) + +diff --git a/src/twisted/trial/reporter.py b/src/twisted/trial/reporter.py +index 98ad7eb1aa6..bfdaca5e907 100644 +--- a/src/twisted/trial/reporter.py ++++ b/src/twisted/trial/reporter.py +@@ -390,6 +390,19 @@ def stopTest(self, test): + """ + return self._originalReporter.stopTest(self.testAdapter(test)) - @classmethod -- def load(Class, requestData, requestFormat=crypto.FILETYPE_ASN1): -- req = crypto.load_certificate_request(requestFormat, requestData) -+ def load( -+ cls, requestData: bytes, requestFormat=crypto.FILETYPE_ASN1 -+ ) -> CertificateRequest: -+ if requestFormat == crypto.FILETYPE_ASN1: -+ req = x509.load_der_x509_csr(requestData) -+ elif requestFormat == crypto.FILETYPE_PEM: -+ req = x509.load_pem_x509_csr(requestData) -+ else: -+ raise ValueError(f"Unsupported format: {requestFormat!r}") -+ if not req.is_signature_valid: -+ subject = req.subject -+ raise VerifyError( -+ f"Can't verify that request for {subject!r} is self-signed." -+ ) -+ return cls(req) -+ -+ def _subjectToDistinguishedName(self) -> DistinguishedName: ++ def addSubTest(self, test, subtest, outcome): + """ -+ Retrieve the subject of this certificate request. ++ See L{unittest.TestResult.addSubTest}. + -+ @return: A copy of the subject of this certificate request. -+ @rtype: L{DistinguishedName} ++ Called by the doctest runner in Python 3.15+ when reporting sub-test results. + """ - dn = DistinguishedName() -- dn._copyFrom(req.get_subject()) -- if not req.verify(req.get_pubkey()): -- raise VerifyError(f"Can't verify that request for {dn!r} is self-signed.") -- return Class(req) -+ for attribute in self.original.subject: -+ try: -+ name = _x509OIDNames[attribute.oid] -+ except KeyError: -+ raise ValueError(f"Unknown X509 name attribute: {attribute.oid!r}") -+ setattr(dn, name, attribute.value) -+ return dn - -- def dump(self, format=crypto.FILETYPE_ASN1): -- return crypto.dump_certificate_request(format, self.original) -+ def dump(self, format=crypto.FILETYPE_ASN1) -> bytes: -+ if format == crypto.FILETYPE_ASN1: -+ return self.original.public_bytes(serialization.Encoding.DER) -+ elif format == crypto.FILETYPE_PEM: -+ return self.original.public_bytes(serialization.Encoding.PEM) -+ else: -+ raise ValueError(f"Unsupported format: {format!r}") - - - class PrivateCertificate(Certificate): -@@ -714,10 +772,21 @@ def newCertificate(self, newCertData, format=crypto.FILETYPE_ASN1): - return PrivateCertificate.load(newCertData, self, format) - - def requestObject(self, distinguishedName, digestAlgorithm="sha256"): -- req = crypto.X509Req() -- req.set_pubkey(self.original) -- distinguishedName._copyInto(req.get_subject()) -- req.sign(self.original, digestAlgorithm) -+ req = ( -+ x509.CertificateSigningRequestBuilder() -+ .subject_name( -+ x509.Name( -+ [ -+ x509.NameAttribute(_x509NameOIDs[k], nativeString(v)) -+ for k, v in distinguishedName.items() -+ ] -+ ) -+ ) -+ .sign( -+ self.original.to_cryptography_key(), -+ _digestAlgorithms[digestAlgorithm](), -+ ) -+ ) - return CertificateRequest(req) - - def certificateRequest( -@@ -750,7 +819,7 @@ def signCertificateRequest( - """ - hlreq = CertificateRequest.load(requestData, requestFormat) - -- dn = hlreq.getSubject() -+ dn = hlreq._subjectToDistinguishedName() - vval = verifyDNCallback(dn) - - def verified(value): -@@ -787,8 +856,8 @@ def signRequestObject( - req = requestObject.original - cert = crypto.X509() - issuerDistinguishedName._copyInto(cert.get_issuer()) -- cert.set_subject(req.get_subject()) -- cert.set_pubkey(req.get_pubkey()) -+ requestObject._subjectToDistinguishedName()._copyInto(cert.get_subject()) -+ cert.set_pubkey(crypto.PKey.from_cryptography_key(req.public_key())) - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(secondsToExpiry) - cert.set_serial_number(serialNumber) -@@ -1656,10 +1725,10 @@ def contextSelectionCallback(connection: SSL.Connection) -> None: - return ctx - - --OpenSSLCertificateOptions.__getstate__ = deprecated( # type:ignore[method-assign] -+OpenSSLCertificateOptions.__getstate__ = deprecated( # type: ignore[method-assign] - Version("Twisted", 15, 0, 0), "a real persistence system" - )(OpenSSLCertificateOptions.__getstate__) --OpenSSLCertificateOptions.__setstate__ = deprecated( # type:ignore[method-assign] -+OpenSSLCertificateOptions.__setstate__ = deprecated( # type: ignore[method-assign] - Version("Twisted", 15, 0, 0), "a real persistence system" - )(OpenSSLCertificateOptions.__setstate__) - -diff --git a/src/twisted/newsfragments/12660.misc b/src/twisted/newsfragments/12660.misc -new file mode 100644 -index 00000000000..e69de29bb2d -diff --git a/src/twisted/test/test_ssl.py b/src/twisted/test/test_ssl.py -index cb5d6925af7..8e2eb423949 100644 ---- a/src/twisted/test/test_ssl.py -+++ b/src/twisted/test/test_ssl.py -@@ -21,6 +21,10 @@ - try: - from OpenSSL import SSL, crypto - -+ from cryptography import x509 -+ from cryptography.hazmat.primitives import hashes, serialization -+ from cryptography.x509.oid import NameOID ++ test = self.testAdapter(test) ++ if hasattr(self._originalReporter, "addSubTest"): ++ return self._originalReporter.addSubTest(test, subtest, outcome) ++ # If the underlying reporter doesn't support subtests, treat as error/success ++ if outcome is not None: ++ return self._originalReporter.addError(test, outcome) + - from twisted.internet import ssl - from twisted.test.ssl_helpers import ClientTLSContext, certPath - except ImportError: -@@ -164,21 +168,31 @@ def generateCertificateObjects(organization, organizationalUnit): - """ - pkey = crypto.PKey() - pkey.generate_key(crypto.TYPE_RSA, 2048) -- req = crypto.X509Req() -- subject = req.get_subject() -- subject.O = organization -- subject.OU = organizationalUnit -- req.set_pubkey(pkey) -- req.sign(pkey, "md5") -+ req = ( -+ x509.CertificateSigningRequestBuilder() -+ .subject_name( -+ x509.Name( -+ [ -+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization), -+ x509.NameAttribute( -+ NameOID.ORGANIZATIONAL_UNIT_NAME, organizationalUnit -+ ), -+ ] -+ ) -+ ) -+ .sign(pkey.to_cryptography_key(), hashes.SHA256()) -+ ) - - # Here comes the actual certificate - cert = crypto.X509() - cert.set_serial_number(1) - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(60) # Testing certificates need not be long lived -- cert.set_issuer(req.get_subject()) -- cert.set_subject(req.get_subject()) -- cert.set_pubkey(req.get_pubkey()) -+ subject = cert.get_subject() -+ subject.O = organization -+ subject.OU = organizationalUnit -+ cert.set_issuer(cert.get_subject()) -+ cert.set_pubkey(pkey) - cert.sign(pkey, "md5") - - return pkey, req, cert -@@ -191,13 +205,13 @@ def generateCertificateFiles(basename, organization, organizationalUnit): - """ - pkey, req, cert = generateCertificateObjects(organization, organizationalUnit) - -- for ext, obj, dumpFunc in [ -- ("key", pkey, crypto.dump_privatekey), -- ("req", req, crypto.dump_certificate_request), -- ("cert", cert, crypto.dump_certificate), -+ for ext, data in [ -+ ("key", crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)), -+ ("req", req.public_bytes(serialization.Encoding.PEM)), -+ ("cert", crypto.dump_certificate(crypto.FILETYPE_PEM, cert)), - ]: - fName = os.extsep.join((basename, ext)).encode("utf-8") -- FilePath(fName).setContent(dumpFunc(crypto.FILETYPE_PEM, obj)) -+ FilePath(fName).setContent(data) - - - class ContextGeneratingMixin: - -From 3fc8db4e7e3924eb3e6b93bb67380bcdb273e17e Mon Sep 17 00:00:00 2001 -From: Alex Gaynor <[email protected]> -Date: Mon, 8 Jun 2026 09:00:00 -0700 -Subject: [PATCH 2/5] Fix mypy errors in CertificateRequest - -Annotate the format parameters and type the original CSR attribute so -public_bytes is no longer inferred as Any. - -Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> ---- - src/twisted/internet/_sslverify.py | 6 ++++-- - 1 file changed, 4 insertions(+), 2 deletions(-) - -diff --git a/src/twisted/internet/_sslverify.py b/src/twisted/internet/_sslverify.py -index f1cec9c2d87..fdfa37adeef 100644 ---- a/src/twisted/internet/_sslverify.py -+++ b/src/twisted/internet/_sslverify.py -@@ -521,9 +521,11 @@ class CertificateRequest(CertBase): - @type original: L{cryptography.x509.CertificateSigningRequest} - """ -+ original: x509.CertificateSigningRequest -+ - @classmethod - def load( -- cls, requestData: bytes, requestFormat=crypto.FILETYPE_ASN1 -+ cls, requestData: bytes, requestFormat: int = crypto.FILETYPE_ASN1 - ) -> CertificateRequest: - if requestFormat == crypto.FILETYPE_ASN1: - req = x509.load_der_x509_csr(requestData) -@@ -554,7 +556,7 @@ def _subjectToDistinguishedName(self) -> DistinguishedName: - setattr(dn, name, attribute.value) - return dn + @implementer(itrial.IReporter) + class Reporter(TestResult): +diff --git a/src/twisted/trial/runner.py b/src/twisted/trial/runner.py +index 6d4fdb96d18..6d780bb3545 100644 +--- a/src/twisted/trial/runner.py ++++ b/src/twisted/trial/runner.py +@@ -760,7 +760,11 @@ def loadFile(self, fileName, recurse=False): + """ + name = reflect.filenameToModuleName(fileName) + try: +- module = SourceFileLoader(name, fileName).load_module() ++ loader = SourceFileLoader(name, fileName) ++ spec = importlib.util.spec_from_loader(name, loader) ++ module = importlib.util.module_from_spec(spec) ++ sys.modules[name] = module ++ spec.loader.exec_module(module) + return self.loadAnything(module, recurse=recurse) + except OSError: + raise ValueError(f"{fileName} is not a Python file.") +diff --git a/src/twisted/trial/test/test_doctest.py b/src/twisted/trial/test/test_doctest.py +index 05d57d44e06..5f6a1c17fe8 100644 +--- a/src/twisted/trial/test/test_doctest.py ++++ b/src/twisted/trial/test/test_doctest.py +@@ -4,6 +4,7 @@ + """ + Test Twisted's doctest support. + """ ++import sys + import unittest as pyunit -- def dump(self, format=crypto.FILETYPE_ASN1) -> bytes: -+ def dump(self, format: int = crypto.FILETYPE_ASN1) -> bytes: - if format == crypto.FILETYPE_ASN1: - return self.original.public_bytes(serialization.Encoding.DER) - elif format == crypto.FILETYPE_PEM: - -From 7cb81ce013455a0adfee8e439c309593bd9d192d Mon Sep 17 00:00:00 2001 -From: Alex Gaynor <[email protected]> -Date: Mon, 8 Jun 2026 09:17:26 -0700 -Subject: [PATCH 3/5] Add CertificateRequest test coverage - -Cover the PEM/DER load and dump branches, unsupported-format and -unverifiable-signature errors, and the unknown subject attribute path. - -Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> ---- - src/twisted/test/test_sslverify.py | 81 ++++++++++++++++++++++++++++++ - 1 file changed, 81 insertions(+) - -diff --git a/src/twisted/test/test_sslverify.py b/src/twisted/test/test_sslverify.py -index c50fb8b1055..579ae1d03f6 100644 ---- a/src/twisted/test/test_sslverify.py -+++ b/src/twisted/test/test_sslverify.py -@@ -51,6 +51,7 @@ - from cryptography import x509 - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives import hashes -+ from cryptography.hazmat.primitives.asymmetric import ec - from cryptography.hazmat.primitives.asymmetric.rsa import ( - RSAPrivateKey, - generate_private_key, -@@ -3472,3 +3473,83 @@ def test_noTrailingNewlinePemCert(self): + from twisted.trial import itrial, reporter, runner, unittest +@@ -40,8 +41,12 @@ def _testRun(self, suite: pyunit.TestSuite) -> None: + """ + result = reporter.TestResult() + suite.run(result) +- self.assertEqual(5, result.successes) +- self.assertEqual(2, len(result.failures)) ++ # Python 3.15+ counts doctest examples differently, resulting in more successes ++ # and also consolidates some failures ++ expected_successes = 7 if sys.version_info >= (3, 15) else 5 ++ expected_failures = 1 if sys.version_info >= (3, 15) else 2 ++ self.assertEqual(expected_successes, result.successes) ++ self.assertEqual(expected_failures, len(result.failures)) - certPEM = noTrailingNewlineKeyPemPath.getContent() - ssl.Certificate.loadPEM(certPEM) -+ -+ -+class CertificateRequestTests(SynchronousTestCase): -+ """ -+ Tests for L{sslverify.CertificateRequest}. -+ """ -+ -+ if skipSSL: -+ skip = skipSSL -+ -+ def _makeRequest(self): -+ """ -+ Create a self-signed L{sslverify.CertificateRequest}. -+ -+ @return: a fresh certificate request. -+ @rtype: L{sslverify.CertificateRequest} -+ """ -+ dn = sslverify.DistinguishedName(commonName="example.twistedmatrix.com") -+ return sslverify.KeyPair.generate().requestObject(dn) -+ -+ def test_pemRoundTrip(self): -+ """ -+ A L{sslverify.CertificateRequest} dumped to PEM format and loaded back -+ again preserves its subject. -+ """ -+ request = self._makeRequest() -+ pem = request.dump(FILETYPE_PEM) -+ self.assertIn(b"BEGIN CERTIFICATE REQUEST", pem) -+ loaded = sslverify.CertificateRequest.load(pem, FILETYPE_PEM) -+ self.assertEqual( -+ loaded._subjectToDistinguishedName(), -+ request._subjectToDistinguishedName(), -+ ) -+ -+ def test_loadUnsupportedFormat(self): -+ """ -+ L{sslverify.CertificateRequest.load} raises L{ValueError} when given an -+ unrecognized format. -+ """ -+ request = self._makeRequest() -+ with self.assertRaises(ValueError): -+ sslverify.CertificateRequest.load(request.dump(), object()) -+ -+ def test_loadUnverifiableSignature(self): -+ """ -+ L{sslverify.CertificateRequest.load} raises L{sslverify.VerifyError} -+ when the request's self-signature does not verify. -+ """ -+ data = bytearray(self._makeRequest().dump()) -+ # Corrupt the trailing signature bytes so the self-signature no longer -+ # verifies while the structure still parses. -+ data[-1] ^= 0xFF -+ with self.assertRaises(sslverify.VerifyError): -+ sslverify.CertificateRequest.load(bytes(data)) -+ -+ def test_dumpUnsupportedFormat(self): -+ """ -+ L{sslverify.CertificateRequest.dump} raises L{ValueError} when given an -+ unrecognized format. -+ """ -+ with self.assertRaises(ValueError): -+ self._makeRequest().dump(object()) -+ -+ def test_subjectUnknownAttribute(self): -+ """ -+ L{sslverify.CertificateRequest._subjectToDistinguishedName} raises -+ L{ValueError} when the subject contains a name attribute that does not -+ correspond to a known L{sslverify.DistinguishedName} field. -+ """ -+ key = ec.generate_private_key(ec.SECP256R1(), backend=default_backend()) -+ csr = ( -+ x509.CertificateSigningRequestBuilder() -+ .subject_name( -+ x509.Name([x509.NameAttribute(NameOID.GIVEN_NAME, "Alice")]) + def test_expectedResults(self, count: int = 1) -> None: + """ +diff --git a/src/twisted/trial/test/test_reporter.py b/src/twisted/trial/test/test_reporter.py +index 76172e7a6de..441704c7662 100644 +--- a/src/twisted/trial/test/test_reporter.py ++++ b/src/twisted/trial/test/test_reporter.py +@@ -205,12 +205,19 @@ def test_doctestError(self): + suite = unittest.decorate(self.loader.loadDoctests(erroneous), itrial.ITestCase) + output = self.getOutput(suite) + path = "twisted.trial.test.erroneous.unexpectedException" +- for substring in ["1/0", "ZeroDivisionError", "Exception raised:", path]: ++ # Python 3.15+ changed doctest error output format ++ if sys.version_info >= (3, 15): ++ substrings = ["1/0", "ZeroDivisionError", path] ++ else: ++ substrings = ["1/0", "ZeroDivisionError", "Exception raised:", path] ++ for substring in substrings: + self.assertSubstring(substring, output) +- self.assertTrue( +- re.search("Fail(ed|ure in) example:", output), +- "Couldn't match 'Failure in example: ' " "or 'Failed example: '", +- ) ++ # Python 3.15+ no longer uses "Failed example:" format ++ if sys.version_info < (3, 15): ++ self.assertTrue( ++ re.search("Fail(ed|ure in) example:", output), ++ "Couldn't match 'Failure in example: ' " "or 'Failed example: '", + ) -+ .sign(key, hashes.SHA256()) -+ ) -+ request = sslverify.CertificateRequest(csr) -+ with self.assertRaises(ValueError): -+ request._subjectToDistinguishedName() - -From 7f5219d25e9eabed51f20f707ac10b1533c7a8c3 Mon Sep 17 00:00:00 2001 -From: Alex Gaynor <[email protected]> -Date: Wed, 10 Jun 2026 06:14:45 -0700 -Subject: [PATCH 4/5] remove comment that's no longer required - ---- - src/twisted/internet/_sslverify.py | 1 - - 1 file changed, 1 deletion(-) - -diff --git a/src/twisted/internet/_sslverify.py b/src/twisted/internet/_sslverify.py -index fdfa37adeef..bd71a401636 100644 ---- a/src/twisted/internet/_sslverify.py -+++ b/src/twisted/internet/_sslverify.py -@@ -518,7 +518,6 @@ class CertificateRequest(CertBase): - returned resulting in an actual certificate. + expect = [self.doubleSeparator, re.compile(r"\[(ERROR|FAIL)\]")] + self.stringComparison(expect, output.splitlines()) - @ivar original: The underlying CSR object. -- @type original: L{cryptography.x509.CertificateSigningRequest} - """ - - original: x509.CertificateSigningRequest - -From b2201db3e08760d4d0ae6563b44109d5c5f555df Mon Sep 17 00:00:00 2001 -From: Alex Gaynor <[email protected]> -Date: Wed, 10 Jun 2026 06:15:39 -0700 -Subject: [PATCH 5/5] fmt - ---- - src/twisted/test/test_sslverify.py | 4 +--- - 1 file changed, 1 insertion(+), 3 deletions(-) - -diff --git a/src/twisted/test/test_sslverify.py b/src/twisted/test/test_sslverify.py -index 579ae1d03f6..282121bc023 100644 ---- a/src/twisted/test/test_sslverify.py -+++ b/src/twisted/test/test_sslverify.py -@@ -3545,9 +3545,7 @@ def test_subjectUnknownAttribute(self): - key = ec.generate_private_key(ec.SECP256R1(), backend=default_backend()) - csr = ( - x509.CertificateSigningRequestBuilder() -- .subject_name( -- x509.Name([x509.NameAttribute(NameOID.GIVEN_NAME, "Alice")]) -- ) -+ .subject_name(x509.Name([x509.NameAttribute(NameOID.GIVEN_NAME, "Alice")])) - .sign(key, hashes.SHA256()) - ) - request = sslverify.CertificateRequest(csr) ++++++ twisted-26.4.0.tar.gz -> twisted-26.10.0.tar.gz ++++++ ++++ 13455 lines of diff (skipped)
