Repository: twisted/twisted

Issue:
type annotate more tests
**Is your feature request related to a problem? Please describe.**
tests are generally safe to type annotate without breaking anyone lets add some more -> Nones


Discussion:

Resolving patch:
diff --git a/src/twisted/python/util.py b/src/twisted/python/util.py
--- a/src/twisted/python/util.py
+++ b/src/twisted/python/util.py
@@ -7,6 +7,7 @@
 import os
 import sys
 import warnings
+from typing import AnyStr
 
 try:
     import grp as _grp
@@ -287,7 +288,9 @@ def addPluginDir():
     sys.path.extend(getPluginDirs())
 
 
-def sibpath(path, sibling):
+def sibpath(
+    path: os.PathLike[AnyStr] | AnyStr, sibling: os.PathLike[AnyStr] | AnyStr
+) -> AnyStr:
     """
     Return the path to a sibling of a file in the filesystem.
 
diff --git a/src/twisted/web/microdom.py b/src/twisted/web/microdom.py
--- a/src/twisted/web/microdom.py
+++ b/src/twisted/web/microdom.py
@@ -1,7 +1,6 @@
 # -*- test-case-name: twisted.web.test.test_xml -*-
 # Copyright (c) Twisted Matrix Laboratories.
 # See LICENSE for details.
-
 """
 Micro Document Object Model: a partial DOM implementation with SUX.
 
@@ -19,6 +18,7 @@
 
 This module is now deprecated.
 """
+from __future__ import annotations
 
 # System Imports
 import re
@@ -326,7 +326,7 @@ def cloneNode(self, deep=0, parent=None):
         d.appendChild(newEl)
         return d
 
-    doctype = None
+    doctype: None | str = None
 
     def isEqualToDocument(self, n):
         return (self.doctype == n.doctype) and Node.isEqualToNode(self, n)


Test patch:
diff --git a/src/twisted/test/test_log.py b/src/twisted/test/test_log.py
--- a/src/twisted/test/test_log.py
+++ b/src/twisted/test/test_log.py
@@ -13,9 +13,14 @@
 import time
 import warnings
 from io import IOBase, StringIO
-from typing import List
+from typing import Callable, List
+
+from zope.interface import implementer
+
+from typing_extensions import Protocol
 
 from twisted.logger import (
+    ILogObserver,
     LogBeginner,
     LoggingFile,
     LogLevel as NewLogLevel,
@@ -39,7 +44,7 @@ class TextFromEventDictTests(unittest.SynchronousTestCase):
     Tests for L{textFromEventDict}.
     """
 
-    def test_message(self):
+    def test_message(self) -> None:
         """
         The C{"message"} value, when specified, is concatenated to generate the
         message.
@@ -48,7 +53,7 @@ def test_message(self):
         text = log.textFromEventDict(eventDict)
         self.assertEqual(text, "a b c")
 
-    def test_format(self):
+    def test_format(self) -> None:
         """
         The C{"format"} value, when specified, is used to format the message.
         """
@@ -56,7 +61,7 @@ def test_format(self):
         text = log.textFromEventDict(eventDict)
         self.assertEqual(text, "Hello, dude!")
 
-    def test_noMessageNoFormat(self):
+    def test_noMessageNoFormat(self) -> None:
         """
         If C{"format"} is unspecified and C{"message"} is empty, return
         L{None}.
@@ -65,7 +70,7 @@ def test_noMessageNoFormat(self):
         text = log.textFromEventDict(eventDict)
         self.assertIsNone(text)
 
-    def test_whySpecified(self):
+    def test_whySpecified(self) -> None:
         """
         The C{"why"} value, when specified, is first part of message.
         """
@@ -76,9 +81,10 @@ def test_whySpecified(self):
                 message=(), isError=1, failure=failure.Failure(), why="foo"
             )
             text = log.textFromEventDict(eventDict)
+            assert text is not None
             self.assertTrue(text.startswith("foo\n"))
 
-    def test_whyDefault(self):
+    def test_whyDefault(self) -> None:
         """
         The C{"why"} value, when unspecified, defaults to C{"Unhandled Error"}.
         """
@@ -87,9 +93,10 @@ def test_whyDefault(self):
         except BaseException:
             eventDict = dict(message=(), isError=1, failure=failure.Failure())
             text = log.textFromEventDict(eventDict)
+            assert text is not None
             self.assertTrue(text.startswith("Unhandled Error\n"))
 
-    def test_noTracebackForYou(self):
+    def test_noTracebackForYou(self) -> None:
         """
         If unable to obtain a traceback due to an exception, catch it and note
         the error.
@@ -101,13 +108,13 @@ def test_noTracebackForYou(self):
 
 
 class LogTests(unittest.SynchronousTestCase):
-    def setUp(self):
-        self.catcher = []
+    def setUp(self) -> None:
+        self.catcher: list[log.EventDict] = []
         self.observer = self.catcher.append
         log.addObserver(self.observer)
         self.addCleanup(log.removeObserver, self.observer)
 
-    def testObservation(self):
+    def testObservation(self) -> None:
         catcher = self.catcher
         log.msg("test", testShouldCatch=True)
         i = catcher.pop()
@@ -116,7 +123,7 @@ def testObservation(self):
         self.assertIn("time", i)
         self.assertEqual(len(catcher), 0)
 
-    def testContext(self):
+    def testContext(self) -> None:
         catcher = self.catcher
         log.callWithContext(
             {"subsystem": "not the default", "subsubsystem": "a", "other": "c"},
@@ -132,7 +139,7 @@ def testContext(self):
         self.assertEqual(i["other"], "d")
         self.assertEqual(i["message"][0], "foo")
 
-    def testErrors(self):
+    def testErrors(self) -> None:
         for e, ig in [
             ("hello world", "hello world"),
             (KeyError(), KeyError),
@@ -143,7 +150,7 @@ def testErrors(self):
             self.assertEqual(i["isError"], 1)
             self.flushLoggedErrors(ig)
 
-    def testErrorsWithWhy(self):
+    def testErrorsWithWhy(self) -> None:
         for e, ig in [
             ("hello world", "hello world"),
             (KeyError(), KeyError),
@@ -155,17 +162,17 @@ def testErrorsWithWhy(self):
             self.assertEqual(i["why"], "foobar")
             self.flushLoggedErrors(ig)
 
-    def test_erroneousErrors(self):
+    def test_erroneousErrors(self) -> None:
         """
         Exceptions raised by log observers are logged but the observer which
         raised the exception remains registered with the publisher.  These
         exceptions do not prevent the event from being sent to other observers
         registered with the publisher.
         """
-        L1 = []
-        L2 = []
+        L1: list[log.EventDict] = []
+        L2: list[log.EventDict] = []
 
-        def broken(event):
+        def broken(event: log.EventDict) -> None:
             1 // 0
 
         for observer in [L1.append, broken, L2.append]:
@@ -194,7 +201,7 @@ def broken(event):
             self.assertEqual(L1[0]["message"], ("Howdy, y'all.",))
             self.assertEqual(L2[0]["message"], ("Howdy, y'all.",))
 
-    def test_showwarning(self):
+    def test_showwarning(self) -> None:
         """
         L{twisted.python.log.showwarning} emits the warning as a message
         to the Twisted logging system.
@@ -234,7 +241,7 @@ def test_showwarning(self):
         )
         self.assertEqual(self.catcher, [])
 
-    def test_warningToFile(self):
+    def test_warningToFile(self) -> None:
         """
         L{twisted.python.log.showwarning} passes warnings with an explicit file
         target on to the underlying Python warning system.
@@ -264,18 +271,18 @@ def test_warningToFile(self):
             warnings.formatwarning(message, category, filename, lineno, line),
         )
 
-    def test_publisherReportsBrokenObserversPrivately(self):
+    def test_publisherReportsBrokenObserversPrivately(self) -> None:
         """
         Log publisher does not use the global L{log.err} when reporting broken
         observers.
         """
         errors = []
 
-        def logError(eventDict):
+        def logError(eventDict: log.EventDict) -> None:
             if eventDict.get("isError"):
                 errors.append(eventDict["failure"].value)
 
-        def fail(eventDict):
+        def fail(eventDict: log.EventDict) -> None:
             raise RuntimeError("test_publisherLocalyReportsBrokenObservers")
 
         publisher = log.LogPublisher()
@@ -289,10 +296,10 @@ def fail(eventDict):
 
 
 class FakeFile(List[bytes]):
-    def write(self, bytes):
+    def write(self, bytes: bytes) -> None:
         self.append(bytes)
 
-    def flush(self):
+    def flush(self) -> None:
         pass
 
 
@@ -316,8 +323,17 @@ class EvilReprStr(EvilStr, EvilRepr):
     pass
 
 
+class _LogPublisherTestCaseMixinBase(Protocol):
+    out: FakeFile
+
+    def assertIsInstance(
+        self, instance: object, classOrTuple: type, message: str
+    ) -> None:
+        ...
+
+
 class LogPublisherTestCaseMixin:
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Add a log observer which records log events in C{self.out}.  Also,
         make sure the default string encoding is ASCII so that
@@ -329,14 +345,7 @@ def setUp(self):
         self.flo = log.FileLogObserver(self.out)
         self.lp.addObserver(self.flo.emit)
 
-        try:
-            "\N{VULGAR FRACTION ONE HALF}"
-        except UnicodeEncodeError:
-            pass
-        # This is the behavior we want - don't change anything.
-        self._origEncoding = None
-
-    def tearDown(self):
+    def tearDown(self: _LogPublisherTestCaseMixinBase) -> None:
         """
         Verify that everything written to the fake file C{self.out} was a
         C{str}.  Also, restore the default string encoding to its previous
@@ -345,24 +354,20 @@ def tearDown(self):
         for chunk in self.out:
             self.assertIsInstance(chunk, str, f"{chunk!r} was not a string")
 
-        if self._origEncoding is not None:
-            sys.setdefaultencoding(self._origEncoding)
-            del sys.setdefaultencoding
-
 
 class LogPublisherTests(LogPublisherTestCaseMixin, unittest.SynchronousTestCase):
-    def testSingleString(self):
+    def testSingleString(self) -> None:
         self.lp.msg("Hello, world.")
         self.assertEqual(len(self.out), 1)
 
-    def testMultipleString(self):
+    def testMultipleString(self) -> None:
         # Test some stupid behavior that will be deprecated real soon.
         # If you are reading this and trying to learn how the logging
         # system works, *do not use this feature*.
         self.lp.msg("Hello, ", "world.")
         self.assertEqual(len(self.out), 1)
 
-    def test_singleUnicode(self):
+    def test_singleUnicode(self) -> None:
         """
         L{log.LogPublisher.msg} does not accept non-ASCII Unicode on Python 2,
         logging an error instead.
@@ -390,7 +395,9 @@ class FileObserverTests(LogPublisherTestCaseMixin, unittest.SynchronousTestCase)
     ERROR_UNFORMATTABLE_SYSTEM = "[UNFORMATTABLE]"
     ERROR_MESSAGE_LOST = "MESSAGE LOST: unformattable object logged"
 
-    def _getTimezoneOffsetTest(self, tzname, daylightOffset, standardOffset):
+    def _getTimezoneOffsetTest(
+        self, tzname: str, daylightOffset: int, standardOffset: int
+    ) -> None:
         """
         Verify that L{getTimezoneOffset} produces the expected offset for a
         certain timezone both when daylight saving time is in effect and when
@@ -458,7 +465,7 @@ def _getTimezoneOffsetTest(self, tzname, daylightOffset, standardOffset):
                 os.environ["TZ"] = originalTimezone
             time.tzset()
 
-    def test_getTimezoneOffsetWestOfUTC(self):
+    def test_getTimezoneOffsetWestOfUTC(self) -> None:
         """
         Attempt to verify that L{FileLogObserver.getTimezoneOffset} returns
         correct values for the current C{TZ} environment setting for at least
@@ -467,7 +474,7 @@ def test_getTimezoneOffsetWestOfUTC(self):
         """
         self._getTimezoneOffsetTest("America/New_York", 14400, 18000)
 
-    def test_getTimezoneOffsetEastOfUTC(self):
+    def test_getTimezoneOffsetEastOfUTC(self) -> None:
         """
         Attempt to verify that L{FileLogObserver.getTimezoneOffset} returns
         correct values for the current C{TZ} environment setting for at least
@@ -476,7 +483,7 @@ def test_getTimezoneOffsetEastOfUTC(self):
         """
         self._getTimezoneOffsetTest("Europe/Berlin", -7200, -3600)
 
-    def test_getTimezoneOffsetWithoutDaylightSavingTime(self):
+    def test_getTimezoneOffsetWithoutDaylightSavingTime(self) -> None:
         """
         Attempt to verify that L{FileLogObserver.getTimezoneOffset} returns
         correct values for the current C{TZ} environment setting for at least
@@ -490,7 +497,7 @@ def test_getTimezoneOffsetWithoutDaylightSavingTime(self):
         # such as glibc>=2.28 return EOVERFLOW.
         self._getTimezoneOffsetTest("Africa/Johannesburg", -7200, -7200)
 
-    def test_timeFormatting(self):
+    def test_timeFormatting(self) -> None:
         """
         Test the method of L{FileLogObserver} which turns a timestamp into a
         human-readable string.
@@ -498,32 +505,32 @@ def test_timeFormatting(self):
         when = calendar.timegm((2001, 2, 3, 4, 5, 6, 7, 8, 0))
 
         # Pretend to be in US/Eastern for a moment
-        self.flo.getTimezoneOffset = lambda when: 18000
+        self.flo.getTimezoneOffset = lambda when: 18000  # type: ignore[method-assign]
         self.assertEqual(self.flo.formatTime(when), "2001-02-02 23:05:06-0500")
 
         # Okay now we're in Eastern Europe somewhere
-        self.flo.getTimezoneOffset = lambda when: -3600
+        self.flo.getTimezoneOffset = lambda when: -3600  # type: ignore[method-assign]
         self.assertEqual(self.flo.formatTime(when), "2001-02-03 05:05:06+0100")
 
         # And off in the Pacific or someplace like that
-        self.flo.getTimezoneOffset = lambda when: -39600
+        self.flo.getTimezoneOffset = lambda when: -39600  # type: ignore[method-assign]
         self.assertEqual(self.flo.formatTime(when), "2001-02-03 15:05:06+1100")
 
         # One of those weird places with a half-hour offset timezone
-        self.flo.getTimezoneOffset = lambda when: 5400
+        self.flo.getTimezoneOffset = lambda when: 5400  # type: ignore[method-assign]
         self.assertEqual(self.flo.formatTime(when), "2001-02-03 02:35:06-0130")
 
         # Half-hour offset in the other direction
-        self.flo.getTimezoneOffset = lambda when: -5400
+        self.flo.getTimezoneOffset = lambda when: -5400  # type: ignore[method-assign]
         self.assertEqual(self.flo.formatTime(when), "2001-02-03 05:35:06+0130")
 
         # Test an offset which is between 0 and 60 minutes to make sure the
         # sign comes out properly in that case.
-        self.flo.getTimezoneOffset = lambda when: 1800
+        self.flo.getTimezoneOffset = lambda when: 1800  # type: ignore[method-assign]
         self.assertEqual(self.flo.formatTime(when), "2001-02-03 03:35:06-0030")
 
         # Test an offset between 0 and 60 minutes in the other direction.
-        self.flo.getTimezoneOffset = lambda when: -1800
+        self.flo.getTimezoneOffset = lambda when: -1800  # type: ignore[method-assign]
         self.assertEqual(self.flo.formatTime(when), "2001-02-03 04:35:06+0030")
 
         # If a strftime-format string is present on the logger, it should
@@ -533,7 +540,7 @@ def test_timeFormatting(self):
         self.flo.timeFormat = "%Y %m"
         self.assertEqual(self.flo.formatTime(when), "2001 02")
 
-    def test_microsecondTimestampFormatting(self):
+    def test_microsecondTimestampFormatting(self) -> None:
         """
         L{FileLogObserver.formatTime} supports a value of C{timeFormat} which
         includes C{"%f"}, a L{datetime}-only format specifier for microseconds.
@@ -541,56 +548,56 @@ def test_microsecondTimestampFormatting(self):
         self.flo.timeFormat = "%f"
         self.assertEqual("600000", self.flo.formatTime(112345.6))
 
-    def test_loggingAnObjectWithBroken__str__(self):
+    def test_loggingAnObjectWithBroken__str__(self) -> None:
         # HELLO, MCFLY
         self.lp.msg(EvilStr())
         self.assertEqual(len(self.out), 1)
         # Logging system shouldn't need to crap itself for this trivial case
         self.assertNotIn(self.ERROR_UNFORMATTABLE_OBJECT, self.out[0])
 
-    def test_formattingAnObjectWithBroken__str__(self):
+    def test_formattingAnObjectWithBroken__str__(self) -> None:
         self.lp.msg(format="%(blat)s", blat=EvilStr())
         self.assertEqual(len(self.out), 1)
         self.assertIn(self.ERROR_INVALID_FORMAT, self.out[0])
 
-    def test_brokenSystem__str__(self):
+    def test_brokenSystem__str__(self) -> None:
         self.lp.msg("huh", system=EvilStr())
         self.assertEqual(len(self.out), 1)
         self.assertIn(self.ERROR_FORMAT, self.out[0])
 
-    def test_formattingAnObjectWithBroken__repr__Indirect(self):
+    def test_formattingAnObjectWithBroken__repr__Indirect(self) -> None:
         self.lp.msg(format="%(blat)s", blat=[EvilRepr()])
         self.assertEqual(len(self.out), 1)
         self.assertIn(self.ERROR_UNFORMATTABLE_OBJECT, self.out[0])
 
-    def test_systemWithBroker__repr__Indirect(self):
+    def test_systemWithBroker__repr__Indirect(self) -> None:
         self.lp.msg("huh", system=[EvilRepr()])
         self.assertEqual(len(self.out), 1)
         self.assertIn(self.ERROR_UNFORMATTABLE_OBJECT, self.out[0])
 
-    def test_simpleBrokenFormat(self):
+    def test_simpleBrokenFormat(self) -> None:
         self.lp.msg(format="hooj %s %s", blat=1)
         self.assertEqual(len(self.out), 1)
         self.assertIn(self.ERROR_INVALID_FORMAT, self.out[0])
 
-    def test_ridiculousFormat(self):
+    def test_ridiculousFormat(self) -> None:
         self.lp.msg(format=42, blat=1)
         self.assertEqual(len(self.out), 1)
         self.assertIn(self.ERROR_INVALID_FORMAT, self.out[0])
 
-    def test_evilFormat__repr__And__str__(self):
+    def test_evilFormat__repr__And__str__(self) -> None:
         self.lp.msg(format=EvilReprStr(), blat=1)
         self.assertEqual(len(self.out), 1)
         self.assertIn(self.ERROR_PATHOLOGICAL, self.out[0])
 
-    def test_strangeEventDict(self):
+    def test_strangeEventDict(self) -> None:
         """
         This kind of eventDict used to fail silently, so test it does.
         """
         self.lp.msg(message="", isError=False)
         self.assertEqual(len(self.out), 0)
 
-    def _startLoggingCleanup(self):
+    def _startLoggingCleanup(self) -> None:
         """
         Cleanup after a startLogging() call that mutates the hell out of some
         global state.
@@ -599,7 +606,7 @@ def _startLoggingCleanup(self):
         self.addCleanup(setattr, sys, "stdout", sys.stdout)
         self.addCleanup(setattr, sys, "stderr", sys.stderr)
 
-    def test_printToStderrSetsIsError(self):
+    def test_printToStderrSetsIsError(self) -> None:
         """
         startLogging()'s overridden sys.stderr should consider everything
         written to it an error.
@@ -608,16 +615,16 @@ def test_printToStderrSetsIsError(self):
         fakeFile = StringIO()
         log.startLogging(fakeFile)
 
-        def observe(event):
+        def observe(event: log.EventDict) -> None:
             observed.append(event)
 
-        observed = []
+        observed: list[log.EventDict] = []
         log.addObserver(observe)
 
         print("Hello, world.", file=sys.stderr)
         self.assertEqual(observed[0]["isError"], 1)
 
-    def test_startLogging(self):
+    def test_startLogging(self) -> None:
         """
         startLogging() installs FileLogObserver and overrides sys.stdout and
         sys.stderr.
@@ -631,19 +638,19 @@ def test_startLogging(self):
         log.msg("Hello!")
         self.assertIn("Hello!", fakeFile.getvalue())
         self.assertIsInstance(sys.stdout, LoggingFile)
-        self.assertEqual(sys.stdout.level, NewLogLevel.info)
+        self.assertEqual(sys.stdout.level, NewLogLevel.info)  # type: ignore[attr-defined]
         encoding = getattr(origStdout, "encoding", None)
         if not encoding:
             encoding = sys.getdefaultencoding()
         self.assertEqual(sys.stdout.encoding.upper(), encoding.upper())
         self.assertIsInstance(sys.stderr, LoggingFile)
-        self.assertEqual(sys.stderr.level, NewLogLevel.error)
+        self.assertEqual(sys.stderr.level, NewLogLevel.error)  # type: ignore[attr-defined]
         encoding = getattr(origStderr, "encoding", None)
         if not encoding:
             encoding = sys.getdefaultencoding()
         self.assertEqual(sys.stderr.encoding.upper(), encoding.upper())
 
-    def test_startLoggingTwice(self):
+    def test_startLoggingTwice(self) -> None:
         """
         There are some obscure error conditions that can occur when logging is
         started twice. See http://twistedmatrix.com/trac/ticket/3289 for more
@@ -655,7 +662,7 @@ def test_startLoggingTwice(self):
         # we use our own LogPublisher, the error doesn't occur.
         sys.stdout = StringIO()
 
-        def showError(eventDict):
+        def showError(eventDict: log.EventDict) -> None:
             if eventDict["isError"]:
                 sys.__stdout__.write(eventDict["failure"].getTraceback())
 
@@ -669,7 +676,7 @@ def showError(eventDict):
         observer = log.startLogging(sys.stdout)
         self.assertIs(sys.stdout, fakeStdout)
 
-    def test_startLoggingOverridesWarning(self):
+    def test_startLoggingOverridesWarning(self) -> None:
         """
         startLogging() overrides global C{warnings.showwarning} such that
         warnings go to Twisted log observers.
@@ -698,7 +705,7 @@ class SysModule:
         self.patch(log, "theLogPublisher", tempLogPublisher)
         # And, one last thing, pretend we're starting from a fresh import, or
         # warnings.warn won't be patched at all.
-        log._oldshowwarning = None
+        log._oldshowwarning = None  # type: ignore[attr-defined]
         # Global mutable state is bad, kids.  Stay in school.
         fakeFile = StringIO()
         # We didn't previously save log messages, so let's make sure we don't
@@ -706,12 +713,15 @@ class SysModule:
         evt = {"pre-start": "event"}
         received = []
 
-        def preStartObserver(x):
-            if "pre-start" in x.keys():
-                received.append(x)
+        @implementer(ILogObserver)
+        class PreStartObserver:
+            def __call__(self, eventDict: log.EventDict) -> None:
+                if "pre-start" in eventDict.keys():
+                    # later we assert that received is empty so this is not covered
+                    received.append(eventDict)  # pragma: no cover
 
         newPublisher(evt)
-        newPublisher.addObserver(preStartObserver)
+        newPublisher.addObserver(PreStartObserver())
         log.startLogging(fakeFile, setStdout=False)
         self.addCleanup(tempLogPublisher._stopLogging)
         self.assertEqual(received, [])
@@ -719,7 +729,7 @@ def preStartObserver(x):
         output = fakeFile.getvalue()
         self.assertIn("UserWarning: hello!", output)
 
-    def test_emitPrefix(self):
+    def test_emitPrefix(self) -> None:
         """
         FileLogObserver.emit() will add a timestamp and system prefix to its
         file output.
@@ -728,7 +738,7 @@ def test_emitPrefix(self):
         flo = log.FileLogObserver(output)
         events = []
 
-        def observer(event):
+        def observer(event: log.EventDict) -> None:
             # Capture the event for reference and pass it along to flo
             events.append(event)
             flo.emit(event)
@@ -751,7 +761,7 @@ def observer(event):
             f"{result!r} does not start with {prefix!r}",
         )
 
-    def test_emitNewline(self):
+    def test_emitNewline(self) -> None:
         """
         FileLogObserver.emit() will append a newline to its file output.
         """
@@ -777,20 +787,20 @@ class PythonLoggingObserverTests(unittest.SynchronousTestCase):
     Test the bridge with python logging module.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         rootLogger = logging.getLogger("")
         originalLevel = rootLogger.getEffectiveLevel()
         rootLogger.setLevel(logging.DEBUG)
 
         @self.addCleanup
-        def restoreLevel():
+        def restoreLevel() -> None:
             rootLogger.setLevel(originalLevel)
 
         self.hdlr, self.out = handlerAndBytesIO()
         rootLogger.addHandler(self.hdlr)
 
         @self.addCleanup
-        def removeLogger():
+        def removeLogger() -> None:
             rootLogger.removeHandler(self.hdlr)
             self.hdlr.close()
 
@@ -798,7 +808,7 @@ def removeLogger():
         self.obs = log.PythonLoggingObserver()
         self.lp.addObserver(self.obs.emit)
 
-    def test_singleString(self):
+    def test_singleString(self) -> None:
         """
         Test simple output, and default log level.
         """
@@ -806,7 +816,7 @@ def test_singleString(self):
         self.assertIn(b"Hello, world.", self.out.getvalue())
         self.assertIn(b"INFO", self.out.getvalue())
 
-    def test_errorString(self):
+    def test_errorString(self) -> None:
         """
         Test error output.
         """
@@ -819,14 +829,14 @@ def test_errorString(self):
             f"Does not start with {prefix!r}: {output!r}",
         )
 
-    def test_formatString(self):
+    def test_formatString(self) -> None:
         """
         Test logging with a format.
         """
         self.lp.msg(format="%(bar)s oo %(foo)s", bar="Hello", foo="world")
         self.assertIn(b"Hello oo world", self.out.getvalue())
 
-    def test_customLevel(self):
+    def test_customLevel(self) -> None:
         """
         Test the logLevel keyword for customizing level used.
         """
@@ -839,7 +849,7 @@ def test_customLevel(self):
         self.assertIn(b"Foo bar.", self.out.getvalue())
         self.assertIn(b"WARNING", self.out.getvalue())
 
-    def test_strangeEventDict(self):
+    def test_strangeEventDict(self) -> None:
         """
         Verify that an event dictionary which is not an error and has an empty
         message isn't recorded.
@@ -853,14 +863,14 @@ class PythonLoggingIntegrationTests(unittest.SynchronousTestCase):
     Test integration of python logging bridge.
     """
 
-    def test_startStopObserver(self):
+    def test_startStopObserver(self) -> None:
         """
         Test that start and stop methods of the observer actually register
         and unregister to the log system.
         """
         oldAddObserver = log.addObserver
         oldRemoveObserver = log.removeObserver
-        l = []
+        l: list[Callable[[log.EventDict], None]] = []
         try:
             log.addObserver = l.append
             log.removeObserver = l.remove
@@ -873,7 +883,7 @@ def test_startStopObserver(self):
             log.addObserver = oldAddObserver
             log.removeObserver = oldRemoveObserver
 
-    def test_inheritance(self):
+    def test_inheritance(self) -> None:
         """
         Test that we can inherit L{log.PythonLoggingObserver} and use super:
         that's basically a validation that L{log.PythonLoggingObserver} is
@@ -881,18 +891,18 @@ def test_inheritance(self):
         """
 
         class MyObserver(log.PythonLoggingObserver):
-            def emit(self, eventDict):
+            def emit(self, eventDict: log.EventDict) -> None:
                 super().emit(eventDict)
 
         obs = MyObserver()
-        l = []
+        l: list[log.EventDict] = []
         oldEmit = log.PythonLoggingObserver.emit
         try:
-            log.PythonLoggingObserver.emit = l.append
-            obs.emit("foo")
-            self.assertEqual(len(l), 1)
+            log.PythonLoggingObserver.emit = l.append  # type: ignore[assignment, method-assign]
+            obs.emit({"foo": "bar"})
+            self.assertEqual(l, [{"foo": "bar"}])
         finally:
-            log.PythonLoggingObserver.emit = oldEmit
+            log.PythonLoggingObserver.emit = oldEmit  # type: ignore[method-assign]
 
 
 class DefaultObserverTests(unittest.SynchronousTestCase):
@@ -900,12 +910,12 @@ class DefaultObserverTests(unittest.SynchronousTestCase):
     Test the default observer.
     """
 
-    def test_failureLogger(self):
+    def test_failureLogger(self) -> None:
         """
         The reason argument passed to log.err() appears in the report
         generated by DefaultObserver.
         """
-        self.catcher = []
+        self.catcher: list[log.EventDict] = []
         self.observer = self.catcher.append
         log.addObserver(self.observer)
         self.addCleanup(log.removeObserver, self.observer)
@@ -922,7 +932,7 @@ def test_failureLogger(self):
         self.assertIn(reason, obs.stderr.getvalue())
         self.assertEqual(len(errors), 1)
 
-    def test_emitEventWithBrokenRepr(self):
+    def test_emitEventWithBrokenRepr(self) -> None:
         """
         DefaultObserver.emit() does not raise when it observes an error event
         with a message that causes L{repr} to raise.
@@ -947,17 +957,17 @@ class StdioOnnaStickTests(unittest.SynchronousTestCase):
     StdioOnnaStick should act like the normal sys.stdout object.
     """
 
-    def setUp(self):
-        self.resultLogs = []
+    def setUp(self) -> None:
+        self.resultLogs: list[log.EventDict] = []
         log.addObserver(self.resultLogs.append)
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         log.removeObserver(self.resultLogs.append)
 
-    def getLogMessages(self):
+    def getLogMessages(self) -> list[str]:
         return ["".join(d["message"]) for d in self.resultLogs]
 
-    def test_write(self):
+    def test_write(self) -> None:
         """
         Writing to a StdioOnnaStick instance results in Twisted log messages.
 
@@ -969,7 +979,7 @@ def test_write(self):
         stdio.write("!\n")
         self.assertEqual(self.getLogMessages(), ["Hello there", "This is a test!"])
 
-    def test_metadata(self):
+    def test_metadata(self) -> None:
         """
         The log messages written by StdioOnnaStick have printed=1 keyword, and
         by default are not errors.
@@ -979,7 +989,7 @@ def test_metadata(self):
         self.assertFalse(self.resultLogs[0]["isError"])
         self.assertTrue(self.resultLogs[0]["printed"])
 
-    def test_writeLines(self):
+    def test_writeLines(self) -> None:
         """
         Writing lines to a StdioOnnaStick results in Twisted log messages.
         """
@@ -987,18 +997,18 @@ def test_writeLines(self):
         stdio.writelines(["log 1", "log 2"])
         self.assertEqual(self.getLogMessages(), ["log 1", "log 2"])
 
-    def test_print(self):
+    def test_print(self) -> None:
         """
         When StdioOnnaStick is set as sys.stdout, prints become log messages.
         """
         oldStdout = sys.stdout
-        sys.stdout = log.StdioOnnaStick()
+        sys.stdout = log.StdioOnnaStick()  # type: ignore[assignment]
         self.addCleanup(setattr, sys, "stdout", oldStdout)
         print("This", end=" ")
         print("is a test")
         self.assertEqual(self.getLogMessages(), ["This is a test"])
 
-    def test_error(self):
+    def test_error(self) -> None:
         """
         StdioOnnaStick created with isError=True log messages as errors.
         """
@@ -1006,7 +1016,7 @@ def test_error(self):
         stdio.write("log 1\n")
         self.assertTrue(self.resultLogs[0]["isError"])
 
-    def test_unicode(self):
+    def test_unicode(self) -> None:
         """
         StdioOnnaStick converts unicode prints to byte strings on Python 2, in
         order to be compatible with the normal stdout/stderr objects.
@@ -1019,7 +1029,7 @@ def test_unicode(self):
         stdio.write(unicodeString + "\n")
         stdio.writelines(["Also, " + unicodeString])
         oldStdout = sys.stdout
-        sys.stdout = stdio
+        sys.stdout = stdio  # type: ignore[assignment]
         self.addCleanup(setattr, sys, "stdout", oldStdout)
         # This should go to the log, utf-8 encoded too:
         print(unicodeString)
diff --git a/src/twisted/test/test_logfile.py b/src/twisted/test/test_logfile.py
--- a/src/twisted/test/test_logfile.py
+++ b/src/twisted/test/test_logfile.py
@@ -1,6 +1,6 @@
 # Copyright (c) Twisted Matrix Laboratories.
 # See LICENSE for details.
-
+from __future__ import annotations
 
 import contextlib
 import errno
@@ -18,13 +18,13 @@ class LogFileTests(TestCase):
     Test the rotating log file.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         self.dir = self.mktemp()
         os.makedirs(self.dir)
         self.name = "test.log"
         self.path = os.path.join(self.dir, self.name)
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         """
         Restore back write rights on created paths: if tests modified the
         rights, that will allow the paths to be removed easily afterwards.
@@ -33,7 +33,7 @@ def tearDown(self):
         if os.path.exists(self.path):
             os.chmod(self.path, 0o777)
 
-    def test_abstractShouldRotate(self):
+    def test_abstractShouldRotate(self) -> None:
         """
         L{BaseLogFile.shouldRotate} is abstract and must be implemented by
         subclass.
@@ -42,7 +42,7 @@ def test_abstractShouldRotate(self):
         self.addCleanup(log.close)
         self.assertRaises(NotImplementedError, log.shouldRotate)
 
-    def test_writing(self):
+    def test_writing(self) -> None:
         """
         Log files can be written to, flushed and closed. Closing a log file
         also flushes it.
@@ -56,7 +56,7 @@ def test_writing(self):
         with open(self.path) as f:
             self.assertEqual(f.read(), "1234567890")
 
-    def test_rotation(self):
+    def test_rotation(self) -> None:
         """
         Rotating log files autorotate after a period of time, and can also be
         manually rotated.
@@ -85,7 +85,7 @@ def test_rotation(self):
 
         self.assertEqual(log.listLogs(), [1, 2, 3])
 
-    def test_append(self):
+    def test_append(self) -> None:
         """
         Log files can be written to, closed. Their size is the number of
         bytes written to them. Everything that was written to them can
@@ -106,7 +106,7 @@ def test_append(self):
         f.seek(0, 0)
         self.assertEqual(f.read(), b"0123456789abc")
 
-    def test_logReader(self):
+    def test_logReader(self) -> None:
         """
         Various tests for log readers.
 
@@ -149,7 +149,7 @@ def test_logReader(self):
             self.assertEqual(reader.readLines(), ["abc\n", "def\n"])
             self.assertEqual(reader.readLines(), [])
 
-    def test_LogReaderReadsZeroLine(self):
+    def test_LogReaderReadsZeroLine(self) -> None:
         """
         L{LogReader.readLines} supports reading no line.
         """
@@ -161,7 +161,7 @@ def test_LogReaderReadsZeroLine(self):
         self.addCleanup(reader.close)
         self.assertEqual([], reader.readLines(0))
 
-    def test_modePreservation(self):
+    def test_modePreservation(self) -> None:
         """
         Check rotated files have same permissions as original.
         """
@@ -174,7 +174,7 @@ def test_modePreservation(self):
         log.rotate()
         self.assertEqual(mode, os.stat(self.path)[stat.ST_MODE])
 
-    def test_noPermission(self):
+    def test_noPermission(self) -> None:
         """
         Check it keeps working when permission on dir changes.
         """
@@ -205,7 +205,7 @@ def test_noPermission(self):
         f.seek(0, 0)
         self.assertEqual(f.read(), b"abcdef")
 
-    def test_maxNumberOfLog(self):
+    def test_maxNumberOfLog(self) -> None:
         """
         Test it respect the limit on the number of files when maxRotatedFiles
         is not None.
@@ -229,7 +229,7 @@ def test_maxNumberOfLog(self):
             self.assertEqual(fp.read(), "2" * 11)
         self.assertFalse(os.path.exists(f"{self.path}.4"))
 
-    def test_fromFullPath(self):
+    def test_fromFullPath(self) -> None:
         """
         Test the fromFullPath method.
         """
@@ -242,7 +242,7 @@ def test_fromFullPath(self):
         self.assertEqual(log1.rotateLength, log2.rotateLength)
         self.assertEqual(log1.defaultMode, log2.defaultMode)
 
-    def test_defaultPermissions(self):
+    def test_defaultPermissions(self) -> None:
         """
         Test the default permission of the log file: if the file exist, it
         should keep the permission.
@@ -254,7 +254,7 @@ def test_defaultPermissions(self):
         self.assertEqual(stat.S_IMODE(os.stat(self.path)[stat.ST_MODE]), currentMode)
         self.addCleanup(log1.close)
 
-    def test_specifiedPermissions(self):
+    def test_specifiedPermissions(self) -> None:
         """
         Test specifying the permissions used on the log file.
         """
@@ -268,7 +268,7 @@ def test_specifiedPermissions(self):
             self.assertEqual(mode, 0o066)
 
     @skipIf(runtime.platform.isWindows(), "Can't test reopen on Windows")
-    def test_reopen(self):
+    def test_reopen(self) -> None:
         """
         L{logfile.LogFile.reopen} allows to rename the currently used file and
         make L{logfile.LogFile} create a new file.
@@ -285,7 +285,7 @@ def test_reopen(self):
         with open(savePath) as f:
             self.assertEqual(f.read(), "hello1")
 
-    def test_nonExistentDir(self):
+    def test_nonExistentDir(self) -> None:
         """
         Specifying an invalid directory to L{LogFile} raises C{IOError}.
         """
@@ -294,7 +294,7 @@ def test_nonExistentDir(self):
         )
         self.assertEqual(e.errno, errno.ENOENT)
 
-    def test_cantChangeFileMode(self):
+    def test_cantChangeFileMode(self) -> None:
         """
         Opening a L{LogFile} which can be read and write but whose mode can't
         be changed doesn't trigger an error.
@@ -312,7 +312,7 @@ def test_cantChangeFileMode(self):
         self.assertEqual(log.path, expectedPath)
         self.assertEqual(log.defaultMode, 0o555)
 
-    def test_listLogsWithBadlyNamedFiles(self):
+    def test_listLogsWithBadlyNamedFiles(self) -> None:
         """
         L{LogFile.listLogs} doesn't choke if it encounters a file with an
         unexpected name.
@@ -327,7 +327,7 @@ def test_listLogsWithBadlyNamedFiles(self):
 
         self.assertEqual([1], log.listLogs())
 
-    def test_listLogsIgnoresZeroSuffixedFiles(self):
+    def test_listLogsIgnoresZeroSuffixedFiles(self) -> None:
         """
         L{LogFile.listLogs} ignores log files which rotated suffix is 0.
         """
@@ -344,12 +344,12 @@ def test_listLogsIgnoresZeroSuffixedFiles(self):
 class RiggedDailyLogFile(logfile.DailyLogFile):
     _clock = 0.0
 
-    def _openFile(self):
+    def _openFile(self) -> None:
         logfile.DailyLogFile._openFile(self)
         # rig the date to match _clock, not mtime
         self.lastDate = self.toDate()
 
-    def toDate(self, *args):
+    def toDate(self, *args: float) -> tuple[int, int, int]:
         if args:
             return time.gmtime(*args)[:3]
         return time.gmtime(self._clock)[:3]
@@ -360,13 +360,13 @@ class DailyLogFileTests(TestCase):
     Test rotating log file.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         self.dir = self.mktemp()
         os.makedirs(self.dir)
         self.name = "testdaily.log"
         self.path = os.path.join(self.dir, self.name)
 
-    def test_writing(self):
+    def test_writing(self) -> None:
         """
         A daily log file can be written to like an ordinary log file.
         """
@@ -379,7 +379,7 @@ def test_writing(self):
         with open(self.path) as f:
             self.assertEqual(f.read(), "1234567890")
 
-    def test_rotation(self):
+    def test_rotation(self) -> None:
         """
         Daily log files rotate daily.
         """
@@ -405,7 +405,7 @@ def test_rotation(self):
         log.write("3")
         self.assertFalse(os.path.exists(days[2]))
 
-    def test_getLog(self):
+    def test_getLog(self) -> None:
         """
         Test retrieving log files with L{DailyLogFile.getLog}.
         """
@@ -432,7 +432,7 @@ def test_getLog(self):
         self.addCleanup(r.close)
         self.assertEqual(data, r.readLines())
 
-    def test_rotateAlreadyExists(self):
+    def test_rotateAlreadyExists(self) -> None:
         """
         L{DailyLogFile.rotate} doesn't do anything if they new log file already
         exists on the disk.
@@ -454,7 +454,7 @@ def test_rotateAlreadyExists(self):
         "Making read-only directories on Windows is too complex for this "
         "test to reasonably do.",
     )
-    def test_rotatePermissionDirectoryNotOk(self):
+    def test_rotatePermissionDirectoryNotOk(self) -> None:
         """
         L{DailyLogFile.rotate} doesn't do anything if the directory containing
         the log files can't be written to.
@@ -469,7 +469,7 @@ def test_rotatePermissionDirectoryNotOk(self):
         log.rotate()
         self.assertEqual(previousFile, log._file)
 
-    def test_rotatePermissionFileNotOk(self):
+    def test_rotatePermissionFileNotOk(self) -> None:
         """
         L{DailyLogFile.rotate} doesn't do anything if the log file can't be
         written to.
@@ -482,7 +482,7 @@ def test_rotatePermissionFileNotOk(self):
         log.rotate()
         self.assertEqual(previousFile, log._file)
 
-    def test_toDate(self):
+    def test_toDate(self) -> None:
         """
         Test that L{DailyLogFile.toDate} converts its timestamp argument to a
         time tuple (year, month, day).
@@ -493,7 +493,7 @@ def test_toDate(self):
         timestamp = time.mktime((2000, 1, 1, 0, 0, 0, 0, 0, 0))
         self.assertEqual((2000, 1, 1), log.toDate(timestamp))
 
-    def test_toDateDefaultToday(self):
+    def test_toDateDefaultToday(self) -> None:
         """
         Test that L{DailyLogFile.toDate} returns today's date by default.
 
@@ -506,7 +506,7 @@ def test_toDateDefaultToday(self):
         date changes between the 2 function calls.
         """
 
-        def mock_localtime(*args):
+        def mock_localtime(*args: object) -> list[int]:
             self.assertEqual((), args)
             return list(range(0, 9))
 
@@ -517,7 +517,7 @@ def mock_localtime(*args):
         logDate = log.toDate()
         self.assertEqual([0, 1, 2], logDate)
 
-    def test_toDateUsesArgumentsToMakeADate(self):
+    def test_toDateUsesArgumentsToMakeADate(self) -> None:
         """
         Test that L{DailyLogFile.toDate} uses its arguments to create a new
         date.
diff --git a/src/twisted/test/test_modules.py b/src/twisted/test/test_modules.py
--- a/src/twisted/test/test_modules.py
+++ b/src/twisted/test/test_modules.py
@@ -5,12 +5,17 @@
 Tests for twisted.python.modules, abstract access to imported or importable
 objects.
 """
-
+from __future__ import annotations
 
 import compileall
 import itertools
 import sys
 import zipfile
+from importlib.abc import PathEntryFinder
+from types import ModuleType
+from typing import Any, Generator
+
+from typing_extensions import Protocol
 
 import twisted
 from twisted.python import modules
@@ -22,12 +27,24 @@
 from twisted.trial.unittest import TestCase
 
 
+class _SupportsWalkModules(Protocol):
+    def walkModules(
+        self, importPackages: bool
+    ) -> Generator[modules.PythonModule, None, None]:
+        ...
+
+
 class TwistedModulesTestCase(TwistedModulesMixin, TestCase):
     """
     Base class for L{modules} test cases.
     """
 
-    def findByIteration(self, modname, where=modules, importPackages=False):
+    def findByIteration(
+        self,
+        modname: str,
+        where: _SupportsWalkModules = modules,
+        importPackages: bool = False,
+    ) -> modules.PythonModule:
         """
         You don't ever actually want to do this, so it's not in the public
         API, but sometimes we want to compare the result of an iterative call
@@ -40,7 +57,7 @@ def findByIteration(self, modname, where=modules, importPackages=False):
 
 
 class BasicTests(TwistedModulesTestCase):
-    def test_namespacedPackages(self):
+    def test_namespacedPackages(self) -> None:
         """
         Duplicate packages are not yielded when iterating over namespace
         packages.
@@ -116,14 +133,14 @@ def test_namespacedPackages(self):
 
         self.assertEqual(walkedNames, expected)
 
-    def test_unimportablePackageGetItem(self):
+    def test_unimportablePackageGetItem(self) -> None:
         """
         If a package has been explicitly forbidden from importing by setting a
         L{None} key in sys.modules under its name,
         L{modules.PythonPath.__getitem__} should still be able to retrieve an
         unloaded L{modules.PythonModule} for that package.
         """
-        shouldNotLoad = []
+        shouldNotLoad: list[str] = []
         path = modules.PythonPath(
             sysPath=[self.pathEntryWithOnePackage().path],
             moduleLoader=shouldNotLoad.append,
@@ -134,7 +151,7 @@ def test_unimportablePackageGetItem(self):
         self.assertEqual(shouldNotLoad, [])
         self.assertFalse(path["test_package"].isLoaded())
 
-    def test_unimportablePackageWalkModules(self):
+    def test_unimportablePackageWalkModules(self) -> None:
         """
         If a package has been explicitly forbidden from importing by setting a
         L{None} key in sys.modules under its name, L{modules.walkModules} should
@@ -143,13 +160,13 @@ def test_unimportablePackageWalkModules(self):
         """
         existentPath = self.pathEntryWithOnePackage()
         self.replaceSysPath([existentPath.path])
-        self.replaceSysModules({"test_package": None})
+        self.replaceSysModules({"test_package": None})  # type: ignore[dict-item]
 
         walked = list(modules.walkModules())
         self.assertEqual([m.name for m in walked], ["test_package"])
         self.assertFalse(walked[0].isLoaded())
 
-    def test_nonexistentPaths(self):
+    def test_nonexistentPaths(self) -> None:
         """
         Verify that L{modules.walkModules} ignores entries in sys.path which
         do not exist in the filesystem.
@@ -170,7 +187,7 @@ def test_nonexistentPaths(self):
         self.assertEqual(beforeModules, expected)
         self.assertEqual(afterModules, expected)
 
-    def test_nonDirectoryPaths(self):
+    def test_nonDirectoryPaths(self) -> None:
         """
         Verify that L{modules.walkModules} ignores entries in sys.path which
         refer to regular files in the filesystem.
@@ -189,7 +206,7 @@ def test_nonDirectoryPaths(self):
 
         self.assertEqual(beforeModules, afterModules)
 
-    def test_twistedShowsUp(self):
+    def test_twistedShowsUp(self) -> None:
         """
         Scrounge around in the top-level module namespace and make sure that
         Twisted shows up, and that the module thusly obtained is the same as
@@ -197,7 +214,7 @@ def test_twistedShowsUp(self):
         """
         self.assertEqual(modules.getModule("twisted"), self.findByIteration("twisted"))
 
-    def test_dottedNames(self):
+    def test_dottedNames(self) -> None:
         """
         Verify that the walkModules APIs will give us back subpackages, not just
         subpackages.
@@ -207,7 +224,7 @@ def test_dottedNames(self):
             self.findByIteration("twisted.python", where=modules.getModule("twisted")),
         )
 
-    def test_onlyTopModules(self):
+    def test_onlyTopModules(self) -> None:
         """
         Verify that the iterModules API will only return top-level modules and
         packages, not submodules or subpackages.
@@ -219,7 +236,7 @@ def test_onlyTopModules(self):
                 % (module.filePath),
             )
 
-    def test_loadPackagesAndModules(self):
+    def test_loadPackagesAndModules(self) -> None:
         """
         Verify that we can locate and load packages, modules, submodules, and
         subpackages.
@@ -229,7 +246,7 @@ def test_loadPackagesAndModules(self):
             self.failUnlessIdentical(modules.getModule(n).load(), m)
             self.failUnlessIdentical(self.findByIteration(n).load(), m)
 
-    def test_pathEntriesOnPath(self):
+    def test_pathEntriesOnPath(self) -> None:
         """
         Verify that path entries discovered via module loading are, in fact, on
         sys.path somewhere.
@@ -237,7 +254,7 @@ def test_pathEntriesOnPath(self):
         for n in ["os", "twisted", "twisted.python", "twisted.python.reflect"]:
             self.failUnlessIn(modules.getModule(n).pathEntry.filePath.path, sys.path)
 
-    def test_alwaysPreferPy(self):
+    def test_alwaysPreferPy(self) -> None:
         """
         Verify that .py files will always be preferred to .pyc files, regardless of
         directory listing order.
@@ -247,11 +264,11 @@ def test_alwaysPreferPy(self):
         pp = modules.PythonPath(sysPath=[mypath.path])
         originalSmartPath = pp._smartPath
 
-        def _evilSmartPath(pathName):
+        def _evilSmartPath(pathName: str) -> Any:
             o = originalSmartPath(pathName)
             originalChildren = o.children
 
-            def evilChildren():
+            def evilChildren() -> Any:
                 # normally this order is random; let's make sure it always
                 # comes up .pyc-first.
                 x = list(originalChildren())
@@ -266,10 +283,10 @@ def evilChildren():
         compileall.compile_dir(mypath.path, quiet=True)
         # sanity check
         self.assertEqual(len(list(mypath.children())), 2)
-        pp._smartPath = _evilSmartPath
+        pp._smartPath = _evilSmartPath  # type: ignore[method-assign]
         self.assertEqual(pp["abcd"].filePath, mypath.child("abcd.py"))
 
-    def test_packageMissingPath(self):
+    def test_packageMissingPath(self) -> None:
         """
         A package can delete its __path__ for some reasons,
         C{modules.PythonPath} should be able to deal with it.
@@ -300,7 +317,7 @@ class PathModificationTests(TwistedModulesTestCase):
     _serialnum = itertools.count()  # used to generate serial numbers for
     # package names.
 
-    def setUp(self):
+    def setUp(self) -> None:
         self.pathExtensionName = self.mktemp()
         self.pathExtension = FilePath(self.pathExtensionName)
         self.pathExtension.createDirectory()
@@ -313,12 +330,12 @@ def setUp(self):
         self.packagePath.child("c__init__.py").setContent(b"")
         self.pathSetUp = False
 
-    def _setupSysPath(self):
+    def _setupSysPath(self) -> None:
         assert not self.pathSetUp
         self.pathSetUp = True
         sys.path.append(self.pathExtensionName)
 
-    def _underUnderPathTest(self, doImport=True):
+    def _underUnderPathTest(self, doImport: bool = True) -> None:
         moddir2 = self.mktemp()
         fpmd = FilePath(moddir2)
         fpmd.createDirectory()
@@ -337,19 +354,19 @@ def _underUnderPathTest(self, doImport=True):
         )
         self.assertEqual(modinfo["foozle"].load().x, 123)
 
-    def test_underUnderPathAlreadyImported(self):
+    def test_underUnderPathAlreadyImported(self) -> None:
         """
         Verify that iterModules will honor the __path__ of already-loaded packages.
         """
         self._underUnderPathTest()
 
-    def _listModules(self):
+    def _listModules(self) -> None:
         pkginfo = modules.getModule(self.packageName)
         nfni = [modinfo.name.split(".")[-1] for modinfo in pkginfo.iterModules()]
         nfni.sort()
         self.assertEqual(nfni, ["a", "b", "c__init__"])
 
-    def test_listingModules(self):
+    def test_listingModules(self) -> None:
         """
         Make sure the module list comes back as we expect from iterModules on a
         package, whether zipped or not.
@@ -357,7 +374,7 @@ def test_listingModules(self):
         self._setupSysPath()
         self._listModules()
 
-    def test_listingModulesAlreadyImported(self):
+    def test_listingModulesAlreadyImported(self) -> None:
         """
         Make sure the module list comes back as we expect from iterModules on a
         package, whether zipped or not, even if the package has already been
@@ -367,7 +384,7 @@ def test_listingModulesAlreadyImported(self):
         namedAny(self.packageName)
         self._listModules()
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         # Intentionally using 'assert' here, this is not a test assertion, this
         # is just an "oh fuck what is going ON" assertion. -glyph
         if self.pathSetUp:
@@ -382,14 +399,14 @@ class RebindingTests(PathModificationTests):
     even when sys.path has been rebound to a different object.
     """
 
-    def _setupSysPath(self):
+    def _setupSysPath(self) -> None:
         assert not self.pathSetUp
         self.pathSetUp = True
         self.savedSysPath = sys.path
         sys.path = sys.path[:]
         sys.path.append(self.pathExtensionName)
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         """
         Clean up sys.path by re-binding our original object.
         """
@@ -398,7 +415,7 @@ def tearDown(self):
 
 
 class ZipPathModificationTests(PathModificationTests):
-    def _setupSysPath(self):
+    def _setupSysPath(self) -> None:
         assert not self.pathSetUp
         zipit(self.pathExtensionName, self.pathExtensionName + ".zip")
         self.pathExtensionName += ".zip"
@@ -412,7 +429,7 @@ class PythonPathTests(TestCase):
     public API of L{twisted.python.modules}, L{PythonPath}.
     """
 
-    def test_unhandledImporter(self):
+    def test_unhandledImporter(self) -> None:
         """
         Make sure that the behavior when encountering an unknown importer
         type is not catastrophic failure.
@@ -421,15 +438,15 @@ def test_unhandledImporter(self):
         class SecretImporter:
             pass
 
-        def hook(name):
+        def hook(name: object) -> SecretImporter:
             return SecretImporter()
 
         syspath = ["example/path"]
-        sysmodules = {}
+        sysmodules: dict[str, ModuleType] = {}
         syshooks = [hook]
-        syscache = {}
+        syscache: dict[str, PathEntryFinder | None] = {}
 
-        def sysloader(name):
+        def sysloader(name: object) -> None:
             return None
 
         space = modules.PythonPath(syspath, sysmodules, syshooks, syscache, sysloader)
@@ -437,7 +454,7 @@ def sysloader(name):
         self.assertEqual(len(entries), 1)
         self.assertRaises(KeyError, lambda: entries[0]["module"])
 
-    def test_inconsistentImporterCache(self):
+    def test_inconsistentImporterCache(self) -> None:
         """
         If the path a module loaded with L{PythonPath.__getitem__} is not
         present in the path importer cache, a warning is emitted, but the
@@ -458,7 +475,7 @@ def test_inconsistentImporterCache(self):
         self.assertEqual(len(warnings), 1)
         self.assertEqual(thisModule.name, __name__)
 
-    def test_containsModule(self):
+    def test_containsModule(self) -> None:
         """
         L{PythonPath} implements the C{in} operator so that when it is the
         right-hand argument and the name of a module which exists on that
@@ -467,7 +484,7 @@ def test_containsModule(self):
         thePath = modules.PythonPath()
         self.assertIn("os", thePath)
 
-    def test_doesntContainModule(self):
+    def test_doesntContainModule(self) -> None:
         """
         L{PythonPath} implements the C{in} operator so that when it is the
         right-hand argument and the name of a module which does not exist on
diff --git a/src/twisted/test/test_paths.py b/src/twisted/test/test_paths.py
--- a/src/twisted/test/test_paths.py
+++ b/src/twisted/test/test_paths.py
@@ -8,9 +8,11 @@
 from __future__ import annotations
 
 import errno
+import io
 import os
 import pickle
 import stat
+import sys
 import time
 from pprint import pformat
 from typing import IO, AnyStr, Callable, Dict, List, Optional, Tuple, TypeVar, Union
@@ -18,6 +20,8 @@
 
 from zope.interface.verify import verifyObject
 
+from typing_extensions import NoReturn
+
 from twisted.python import filepath
 from twisted.python.filepath import FileMode, OtherAnyStr
 from twisted.python.runtime import platform
@@ -47,15 +51,15 @@ class AbstractFilePathTests(BytesTestCase):
     f1content = b"file 1"
     f2content = b"file 2"
 
-    def _mkpath(self, *p):
+    def _mkpath(self, *p: bytes) -> bytes:
         x = os.path.abspath(os.path.join(self.cmn, *p))
         self.all.append(x)
         return x
 
-    def subdir(self, *dirname):
+    def subdir(self, *dirname: bytes) -> None:
         os.mkdir(self._mkpath(*dirname))
 
-    def subfile(self, *dirname):
+    def subfile(self, *dirname: bytes) -> io.BufferedWriter:
         return open(self._mkpath(*dirname), "wb")
 
     def setUp(self) -> None:
@@ -254,13 +258,19 @@ def listdir(self) -> List[AnyStr]:
         )
 
 
+if sys.platform != "win32":
+
+    class WindowsError:
+        pass
+
+
 class ListingCompatibilityTests(BytesTestCase):
     """
     These tests verify compatibility with legacy behavior of directory listing.
     """
 
     @skipIf(not platform.isWindows(), "Only relevant on on Windows.")
-    def test_windowsErrorExcept(self):
+    def test_windowsErrorExcept(self) -> None:
         """
         Verify that when a WindowsError is raised from listdir, catching
         WindowsError works.
@@ -303,13 +313,13 @@ class ExplodingFile:
 
     closed = False
 
-    def read(self, n=0):
+    def read(self, n: int = 0) -> NoReturn:
         """
         @raise IOError: Always raised.
         """
         raise OSError()
 
-    def write(self, what):
+    def write(self, what: bytes) -> NoReturn:
         """
         @raise IOError: Always raised.
         """
diff --git a/src/twisted/test/test_persisted.py b/src/twisted/test/test_persisted.py
--- a/src/twisted/test/test_persisted.py
+++ b/src/twisted/test/test_persisted.py
@@ -1,6 +1,6 @@
 # Copyright (c) Twisted Matrix Laboratories.
 # See LICENSE for details.
-
+from __future__ import annotations
 
 # System Imports
 import copyreg
@@ -8,6 +8,9 @@
 import pickle
 import sys
 import textwrap
+from typing import Any, Callable, List, Tuple
+
+from typing_extensions import NoReturn
 
 # Twisted Imports
 from twisted.persisted import aot, crefutil, styles
@@ -16,42 +19,43 @@
 
 
 class VersionTests(TestCase):
-    def test_nullVersionUpgrade(self):
+    def test_nullVersionUpgrade(self) -> None:
         global NullVersioned
 
         class NullVersioned:
-            def __init__(self):
+            def __init__(self) -> None:
                 self.ok = 0
 
         pkcl = pickle.dumps(NullVersioned())
 
-        class NullVersioned(styles.Versioned):
+        class NullVersioned(styles.Versioned):  # type: ignore[no-redef]
             persistenceVersion = 1
 
-            def upgradeToVersion1(self):
+            def upgradeToVersion1(self) -> None:
                 self.ok = 1
 
         mnv = pickle.loads(pkcl)
         styles.doUpgrade()
         assert mnv.ok, "initial upgrade not run!"
 
-    def test_versionUpgrade(self):
+    def test_versionUpgrade(self) -> None:
         global MyVersioned
 
         class MyVersioned(styles.Versioned):
             persistenceVersion = 2
-            persistenceForgets = ["garbagedata"]
+            # persistenceForgets should be a tuple
+            persistenceForgets = ["garbagedata"]  # type: ignore[assignment]
             v3 = 0
             v4 = 0
 
-            def __init__(self):
+            def __init__(self) -> None:
                 self.somedata = "xxx"
                 self.garbagedata = lambda q: "cant persist"
 
-            def upgradeToVersion3(self):
+            def upgradeToVersion3(self) -> None:
                 self.v3 += 1
 
-            def upgradeToVersion4(self):
+            def upgradeToVersion4(self) -> None:
                 self.v4 += 1
 
         mv = MyVersioned()
@@ -68,15 +72,15 @@ def upgradeToVersion4(self):
         assert obj.v3 == 1, "upgraded unnecessarily"
         assert obj.v4 == 1, "upgraded unnecessarily"
 
-    def test_nonIdentityHash(self):
+    def test_nonIdentityHash(self) -> None:
         global ClassWithCustomHash
 
         class ClassWithCustomHash(styles.Versioned):
-            def __init__(self, unique, hash):
+            def __init__(self, unique: str, hash: int) -> None:
                 self.unique = unique
                 self.hash = hash
 
-            def __hash__(self):
+            def __hash__(self) -> int:
                 return self.hash
 
         v1 = ClassWithCustomHash("v1", 0)
@@ -85,17 +89,17 @@ def __hash__(self):
         pkl = pickle.dumps((v1, v2))
         del v1, v2
         ClassWithCustomHash.persistenceVersion = 1
-        ClassWithCustomHash.upgradeToVersion1 = lambda self: setattr(
+        ClassWithCustomHash.upgradeToVersion1 = lambda self: setattr(  # type: ignore[attr-defined]
             self, "upgraded", True
         )
         v1, v2 = pickle.loads(pkl)
         styles.doUpgrade()
         self.assertEqual(v1.unique, "v1")
         self.assertEqual(v2.unique, "v2")
-        self.assertTrue(v1.upgraded)
-        self.assertTrue(v2.upgraded)
+        self.assertTrue(v1.upgraded)  # type: ignore[attr-defined]
+        self.assertTrue(v2.upgraded)  # type: ignore[attr-defined]
 
-    def test_upgradeDeserializesObjectsRequiringUpgrade(self):
+    def test_upgradeDeserializesObjectsRequiringUpgrade(self) -> None:
         global ToyClassA, ToyClassB
 
         class ToyClassA(styles.Versioned):
@@ -110,21 +114,21 @@ class ToyClassB(styles.Versioned):
         del x, y
         ToyClassA.persistenceVersion = 1
 
-        def upgradeToVersion1(self):
+        def upgradeToVersion1(self: Any) -> None:
             self.y = pickle.loads(pklB)
             styles.doUpgrade()
 
-        ToyClassA.upgradeToVersion1 = upgradeToVersion1
+        ToyClassA.upgradeToVersion1 = upgradeToVersion1  # type: ignore[attr-defined]
         ToyClassB.persistenceVersion = 1
 
-        def setUpgraded(self):
+        def setUpgraded(self: object) -> None:
             setattr(self, "upgraded", True)
 
-        ToyClassB.upgradeToVersion1 = setUpgraded
+        ToyClassB.upgradeToVersion1 = setUpgraded  # type: ignore[attr-defined]
 
         x = pickle.loads(pklA)
         styles.doUpgrade()
-        self.assertTrue(x.y.upgraded)
+        self.assertTrue(x.y.upgraded)  # type: ignore[attr-defined]
 
 
 class VersionedSubClass(styles.Versioned):
@@ -149,27 +153,27 @@ class AybabtuTests(TestCase):
     argument that are strictly between L{Versioned} and the class itself.
     """
 
-    def test_aybabtuStrictEmpty(self):
+    def test_aybabtuStrictEmpty(self) -> None:
         """
         L{styles._aybabtu} of L{Versioned} itself is an empty list.
         """
         self.assertEqual(styles._aybabtu(styles.Versioned), [])
 
-    def test_aybabtuStrictSubclass(self):
+    def test_aybabtuStrictSubclass(self) -> None:
         """
         There are no classes I{between} L{VersionedSubClass} and L{Versioned},
         so L{styles._aybabtu} returns an empty list.
         """
         self.assertEqual(styles._aybabtu(VersionedSubClass), [])
 
-    def test_aybabtuSubsubclass(self):
+    def test_aybabtuSubsubclass(self) -> None:
         """
         With a sub-sub-class of L{Versioned}, L{styles._aybabtu} returns a list
         containing the intervening subclass.
         """
         self.assertEqual(styles._aybabtu(VersionedSubSubClass), [VersionedSubClass])
 
-    def test_aybabtuStrict(self):
+    def test_aybabtuStrict(self) -> None:
         """
         For a diamond-shaped inheritance graph, L{styles._aybabtu} returns a
         list containing I{both} intermediate subclasses.
@@ -181,12 +185,12 @@ def test_aybabtuStrict(self):
 
 
 class MyEphemeral(styles.Ephemeral):
-    def __init__(self, x):
+    def __init__(self, x: int) -> None:
         self.x = x
 
 
 class EphemeralTests(TestCase):
-    def test_ephemeral(self):
+    def test_ephemeral(self) -> None:
         o = MyEphemeral(3)
         self.assertEqual(o.__class__, MyEphemeral)
         self.assertEqual(o.x, 3)
@@ -199,10 +203,10 @@ def test_ephemeral(self):
 
 
 class Pickleable:
-    def __init__(self, x):
+    def __init__(self, x: int) -> None:
         self.x = x
 
-    def getX(self):
+    def getX(self) -> int:
         return self.x
 
 
@@ -211,7 +215,7 @@ class NotPickleable:
     A class that is not pickleable.
     """
 
-    def __reduce__(self):
+    def __reduce__(self) -> NoReturn:
         """
         Raise an exception instead of pickling.
         """
@@ -224,7 +228,7 @@ class CopyRegistered:
     C{copyreg} module.
     """
 
-    def __init__(self):
+    def __init__(self) -> None:
         """
         Ensure that this object is normally not pickleable.
         """
@@ -237,7 +241,7 @@ class CopyRegisteredLoaded:
     """
 
 
-def reduceCopyRegistered(cr):
+def reduceCopyRegistered(cr: object) -> tuple[type[CopyRegisteredLoaded], tuple[()]]:
     """
     Externally implement C{__reduce__} for L{CopyRegistered}.
 
@@ -249,7 +253,7 @@ def reduceCopyRegistered(cr):
     return CopyRegisteredLoaded, ()
 
 
-copyreg.pickle(CopyRegistered, reduceCopyRegistered)
+copyreg.pickle(CopyRegistered, reduceCopyRegistered)  # type: ignore[arg-type]
 
 
 class A:
@@ -257,7 +261,9 @@ class A:
     dummy class
     """
 
-    def amethod(self):
+    bmethod: Callable[[], None]
+
+    def amethod(self) -> None:
         pass
 
 
@@ -266,23 +272,25 @@ class B:
     dummy class
     """
 
-    def bmethod(self):
+    a: A
+
+    def bmethod(self) -> None:
         pass
 
 
-def funktion():
+def funktion() -> None:
     pass
 
 
 class PicklingTests(TestCase):
     """Test pickling of extra object types."""
 
-    def test_module(self):
+    def test_module(self) -> None:
         pickl = pickle.dumps(styles)
         o = pickle.loads(pickl)
         self.assertEqual(o, styles)
 
-    def test_instanceMethod(self):
+    def test_instanceMethod(self) -> None:
         obj = Pickleable(4)
         pickl = pickle.dumps(obj.getX)
         o = pickle.loads(pickl)
@@ -296,7 +304,7 @@ class StringIOTransitionTests(TestCase):
     StringIO in Python 3, depending on the type of its contents.
     """
 
-    def test_unpickleBytesIO(self):
+    def test_unpickleBytesIO(self) -> None:
         """
         A cStringIO pickled with bytes in it will yield an L{io.BytesIO} on
         python 3.
@@ -311,22 +319,31 @@ def test_unpickleBytesIO(self):
 
 
 class EvilSourceror:
-    def __init__(self, x):
+    a: EvilSourceror
+    b: EvilSourceror
+    c: object
+
+    def __init__(self, x: object) -> None:
         self.a = self
         self.a.b = self
         self.a.b.c = x
 
 
 class NonDictState:
-    def __getstate__(self):
+    state: str
+
+    def __getstate__(self) -> str:
         return self.state
 
-    def __setstate__(self, state):
+    def __setstate__(self, state: str) -> None:
         self.state = state
 
 
+_CircularTupleType = List[Tuple["_CircularTupleType", int]]
+
+
 class AOTTests(TestCase):
-    def test_simpleTypes(self):
+    def test_simpleTypes(self) -> None:
         obj = (
             1,
             2.0,
@@ -342,7 +359,7 @@ def test_simpleTypes(self):
         rtObj = aot.unjellyFromSource(aot.jellyToSource(obj))
         self.assertEqual(obj, rtObj)
 
-    def test_methodSelfIdentity(self):
+    def test_methodSelfIdentity(self) -> None:
         a = A()
         b = B()
         a.bmethod = b.bmethod
@@ -350,7 +367,7 @@ def test_methodSelfIdentity(self):
         im_ = aot.unjellyFromSource(aot.jellyToSource(b)).a.bmethod
         self.assertEqual(aot._selfOfMethod(im_).__class__, aot._classOfMethod(im_))
 
-    def test_methodNotSelfIdentity(self):
+    def test_methodNotSelfIdentity(self) -> None:
         """
         If a class change after an instance has been created,
         L{aot.unjellyFromSource} shoud raise a C{TypeError} when trying to
@@ -365,9 +382,9 @@ def test_methodNotSelfIdentity(self):
         try:
             self.assertRaises(TypeError, aot.unjellyFromSource, aot.jellyToSource(b))
         finally:
-            B.bmethod = savedbmethod
+            B.bmethod = savedbmethod  # type: ignore[method-assign]
 
-    def test_unsupportedType(self):
+    def test_unsupportedType(self) -> None:
         """
         L{aot.jellyToSource} should raise a C{TypeError} when trying to jelly
         an unknown type without a C{__dict__} property or C{__getstate__}
@@ -376,16 +393,16 @@ def test_unsupportedType(self):
 
         class UnknownType:
             @property
-            def __dict__(self):
+            def __dict__(self) -> NoReturn:  # type: ignore[override]
                 raise AttributeError()
 
             @property
-            def __getstate__(self):
+            def __getstate__(self) -> NoReturn:
                 raise AttributeError()
 
         self.assertRaises(TypeError, aot.jellyToSource, UnknownType())
 
-    def test_basicIdentity(self):
+    def test_basicIdentity(self) -> None:
         # Anyone wanting to make this datastructure more complex, and thus this
         # test more comprehensive, is welcome to do so.
         aj = aot.AOTJellier().jellyToAO
@@ -412,12 +429,12 @@ def test_basicIdentity(self):
         assert uj[0] is uj[1]
         assert uj[1][0:5] == l[0:5]
 
-    def test_nonDictState(self):
+    def test_nonDictState(self) -> None:
         a = NonDictState()
         a.state = "meringue!"
         assert aot.unjellyFromSource(aot.jellyToSource(a)).state == a.state
 
-    def test_copyReg(self):
+    def test_copyReg(self) -> None:
         """
         L{aot.jellyToSource} and L{aot.unjellyFromSource} honor functions
         registered in the pickle copy registry.
@@ -425,7 +442,7 @@ def test_copyReg(self):
         uj = aot.unjellyFromSource(aot.jellyToSource(CopyRegistered()))
         self.assertIsInstance(uj, CopyRegisteredLoaded)
 
-    def test_funkyReferences(self):
+    def test_funkyReferences(self) -> None:
         o = EvilSourceror(EvilSourceror([]))
         j1 = aot.jellyToAOT(o)
         oj = aot.unjellyFromAOT(j1)
@@ -434,11 +451,11 @@ def test_funkyReferences(self):
         assert oj.a.b is oj.b
         assert oj.c is not oj.c.c
 
-    def test_circularTuple(self):
+    def test_circularTuple(self) -> None:
         """
         L{aot.jellyToAOT} can persist circular references through tuples.
         """
-        l = []
+        l: _CircularTupleType = []
         t = (l, 4321)
         l.append(t)
         j1 = aot.jellyToAOT(l)
@@ -447,7 +464,7 @@ def test_circularTuple(self):
         self.assertIs(oj[0][0], oj)
         self.assertEqual(oj[0][1], 4321)
 
-    def testIndentify(self):
+    def testIndentify(self) -> None:
         """
         The generated serialization is indented.
         """
@@ -469,14 +486,14 @@ class CrefUtilTests(TestCase):
     Tests for L{crefutil}.
     """
 
-    def test_dictUnknownKey(self):
+    def test_dictUnknownKey(self) -> None:
         """
         L{crefutil._DictKeyAndValue} only support keys C{0} and C{1}.
         """
         d = crefutil._DictKeyAndValue({})
         self.assertRaises(RuntimeError, d.__setitem__, 2, 3)
 
-    def test_deferSetMultipleTimes(self):
+    def test_deferSetMultipleTimes(self) -> None:
         """
         L{crefutil._Defer} can be assigned a key only one time.
         """
@@ -484,7 +501,7 @@ def test_deferSetMultipleTimes(self):
         d[0] = 1
         self.assertRaises(RuntimeError, d.__setitem__, 0, 1)
 
-    def test_containerWhereAllElementsAreKnown(self):
+    def test_containerWhereAllElementsAreKnown(self) -> None:
         """
         A L{crefutil._Container} where all of its elements are known at
         construction time is nonsensical and will result in errors in any call
@@ -493,7 +510,7 @@ def test_containerWhereAllElementsAreKnown(self):
         container = crefutil._Container([1, 2, 3], list)
         self.assertRaises(AssertionError, container.addDependant, {}, "ignore-me")
 
-    def test_dontPutCircularReferencesInDictionaryKeys(self):
+    def test_dontPutCircularReferencesInDictionaryKeys(self) -> None:
         """
         If a dictionary key contains a circular reference (which is probably a
         bad practice anyway) it will be resolved by a
@@ -504,7 +521,7 @@ def test_dontPutCircularReferencesInDictionaryKeys(self):
             AssertionError, dict().__setitem__, crefutil.NotKnown(), "value"
         )
 
-    def test_dontCallInstanceMethodsThatArentReady(self):
+    def test_dontCallInstanceMethodsThatArentReady(self) -> None:
         """
         L{crefutil._InstanceMethod} raises L{AssertionError} to indicate it
         should not be called.  This should not be possible with any of its API
diff --git a/src/twisted/test/test_plugin.py b/src/twisted/test/test_plugin.py
--- a/src/twisted/test/test_plugin.py
+++ b/src/twisted/test/test_plugin.py
@@ -5,7 +5,7 @@
 """
 Tests for Twisted plugin system.
 """
-
+from __future__ import annotations
 
 import compileall
 import errno
@@ -14,13 +14,14 @@
 import sys
 import time
 from importlib import invalidate_caches as invalidateImportCaches
-from typing import Callable, TypeVar
+from types import ModuleType
+from typing import Callable, TypedDict, TypeVar
 
 from zope.interface import Interface
 
 from twisted import plugin
 from twisted.python.filepath import FilePath
-from twisted.python.log import addObserver, removeObserver, textFromEventDict
+from twisted.python.log import EventDict, addObserver, removeObserver, textFromEventDict
 from twisted.trial import unittest
 
 _T = TypeVar("_T")
@@ -46,7 +47,7 @@ class PluginTests(unittest.TestCase):
     directory.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Save C{sys.path} and C{sys.modules}, and create a package for tests.
         """
@@ -70,7 +71,7 @@ def setUp(self):
 
         self.module = mypackage
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         """
         Restore C{sys.path} and C{sys.modules} to their original values.
         """
@@ -78,7 +79,10 @@ def tearDown(self):
         sys.modules.clear()
         sys.modules.update(self.savedModules)
 
-    def _unimportPythonModule(self, module, deleteSource=False):
+    def _unimportPythonModule(
+        self, module: ModuleType, deleteSource: bool = False
+    ) -> None:
+        assert module.__file__ is not None
         modulePath = module.__name__.split(".")
         packageName = ".".join(modulePath[:-1])
         moduleName = modulePath[-1]
@@ -88,17 +92,18 @@ def _unimportPythonModule(self, module, deleteSource=False):
         for ext in ["c", "o"] + (deleteSource and [""] or []):
             try:
                 os.remove(module.__file__ + ext)
-            except OSError as ose:
-                if ose.errno != errno.ENOENT:
-                    raise
+            except FileNotFoundError:
+                pass
 
-    def _clearCache(self):
+    def _clearCache(self) -> None:
         """
         Remove the plugins B{droping.cache} file.
         """
         self.package.child("dropin.cache").remove()
 
-    def _withCacheness(meth: Callable[[_T], object]) -> Callable[[_T], None]:
+    def _withCacheness(
+        meth: Callable[[PluginTests], object]
+    ) -> Callable[[PluginTests], None]:
         """
         This is a paranoid test wrapper, that calls C{meth} 2 times, clear the
         cache, and calls it 2 other times. It's supposed to ensure that the
@@ -107,7 +112,7 @@ def _withCacheness(meth: Callable[[_T], object]) -> Callable[[_T], None]:
         """
 
         @functools.wraps(meth)
-        def wrapped(self):
+        def wrapped(self: PluginTests) -> None:
             meth(self)
             meth(self)
             self._clearCache()
@@ -117,7 +122,7 @@ def wrapped(self):
         return wrapped
 
     @_withCacheness
-    def test_cache(self):
+    def test_cache(self) -> None:
         """
         Check that the cache returned by L{plugin.getCache} hold the plugin
         B{testplugin}, and that this plugin has the properties we expect:
@@ -153,7 +158,7 @@ def test_cache(self):
 
         self.assertIs(realPlugin, tp.TestPlugin)
 
-    def test_cacheRepr(self):
+    def test_cacheRepr(self) -> None:
         """
         L{CachedPlugin} has a helpful C{repr} which contains relevant
         information about it.
@@ -169,7 +174,7 @@ def test_cacheRepr(self):
         )
 
     @_withCacheness
-    def test_plugins(self):
+    def test_plugins(self) -> None:
         """
         L{plugin.getPlugins} should return the list of plugins matching the
         specified interface (here, L{ITestPlugin2}), and these plugins
@@ -182,11 +187,11 @@ def test_plugins(self):
 
         names = ["AnotherTestPlugin", "ThirdTestPlugin"]
         for p in plugins:
-            names.remove(p.__name__)
-            p.test()
+            names.remove(p.__name__)  # type: ignore[attr-defined]
+            p.test()  # type: ignore[attr-defined]
 
     @_withCacheness
-    def test_detectNewFiles(self):
+    def test_detectNewFiles(self) -> None:
         """
         Check that L{plugin.getPlugins} is able to detect plugins added at
         runtime.
@@ -210,13 +215,13 @@ def test_detectNewFiles(self):
 
             names = ["TestPlugin", "FourthTestPlugin"]
             for p in plgs:
-                names.remove(p.__name__)
-                p.test1()
+                names.remove(p.__name__)  # type: ignore[attr-defined]
+                p.test1()  # type: ignore[attr-defined]
         finally:
             self._unimportPythonModule(sys.modules["mypackage.pluginextra"], True)
 
     @_withCacheness
-    def test_detectFilesChanged(self):
+    def test_detectFilesChanged(self) -> None:
         """
         Check that if the content of a plugin change, L{plugin.getPlugins} is
         able to detect the new plugins added.
@@ -243,13 +248,13 @@ def test_detectFilesChanged(self):
 
             names = ["TestPlugin", "FourthTestPlugin", "FifthTestPlugin"]
             for p in plgs:
-                names.remove(p.__name__)
-                p.test1()
+                names.remove(p.__name__)  # type: ignore[attr-defined]
+                p.test1()  # type: ignore[attr-defined]
         finally:
             self._unimportPythonModule(sys.modules["mypackage.pluginextra"], True)
 
     @_withCacheness
-    def test_detectFilesRemoved(self):
+    def test_detectFilesRemoved(self) -> None:
         """
         Check that when a dropin file is removed, L{plugin.getPlugins} doesn't
         return it anymore.
@@ -267,7 +272,7 @@ def test_detectFilesRemoved(self):
         self.assertEqual(1, len(plgs))
 
     @_withCacheness
-    def test_nonexistentPathEntry(self):
+    def test_nonexistentPathEntry(self) -> None:
         """
         Test that getCache skips over any entries in a plugin package's
         C{__path__} which do not exist.
@@ -283,7 +288,7 @@ def test_nonexistentPathEntry(self):
             self.module.__path__.remove(path)
 
     @_withCacheness
-    def test_nonDirectoryChildEntry(self):
+    def test_nonDirectoryChildEntry(self) -> None:
         """
         Test that getCache skips over any entries in a plugin package's
         C{__path__} which refer to children of paths which are not directories.
@@ -299,7 +304,7 @@ def test_nonDirectoryChildEntry(self):
         finally:
             self.module.__path__.remove(child)
 
-    def test_deployedMode(self):
+    def test_deployedMode(self) -> None:
         """
         The C{dropin.cache} file may not be writable: the cache should still be
         attainable, but an error should be logged to show that the cache
@@ -323,7 +328,7 @@ def test_deployedMode(self):
         self.addCleanup(os.chmod, cachepath.path, 0o700)
 
         # Start observing log events to see the warning
-        events = []
+        events: list[EventDict] = []
         addObserver(events.append)
         self.addCleanup(removeObserver, events.append)
 
@@ -338,9 +343,11 @@ def test_deployedMode(self):
             errno.EPERM,
         )
         for event in events:
-            if expected in textFromEventDict(event):
+            maybeText = textFromEventDict(event)
+            assert maybeText is not None
+            if expected in maybeText:  # pragma: no branch
                 break
-        else:
+        else:  # pragma: no cover
             self.fail(
                 "Did not observe unwriteable cache warning in log "
                 "events: %r" % (events,)
@@ -355,7 +362,7 @@ def test_deployedMode(self):
 """
 
 
-def pluginFileContents(name):
+def pluginFileContents(name: str) -> bytes:
     return (
         (
             "from zope.interface import provider\n"
@@ -371,7 +378,9 @@ def pluginFileContents(name):
     )
 
 
-def _createPluginDummy(entrypath, pluginContent, real, pluginModule):
+def _createPluginDummy(
+    entrypath: FilePath[str], pluginContent: bytes, real: bool, pluginModule: str
+) -> FilePath[str]:
     """
     Create a plugindummy package.
     """
@@ -388,6 +397,10 @@ def _createPluginDummy(entrypath, pluginContent, real, pluginModule):
     return plugs
 
 
+class _HasBoolLegacyKey(TypedDict):
+    legacy: bool
+
+
 class DeveloperSetupTests(unittest.TestCase):
     """
     These tests verify things about the plugin system without actually
@@ -395,7 +408,7 @@ class DeveloperSetupTests(unittest.TestCase):
     temporary package.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Create a complex environment with multiple entries on sys.path, akin to
         a developer's environment who has a development (trunk) checkout of
@@ -437,21 +450,21 @@ def setUp(self):
         self.lockSystem()
         self.resetEnvironment()
 
-    def lockSystem(self):
+    def lockSystem(self) -> None:
         """
         Lock the system directories, as if they were unwritable by this user.
         """
         os.chmod(self.sysplug.path, 0o555)
         os.chmod(self.syscache.path, 0o555)
 
-    def unlockSystem(self):
+    def unlockSystem(self) -> None:
         """
         Unlock the system directories, as if they were writable by this user.
         """
         os.chmod(self.sysplug.path, 0o777)
         os.chmod(self.syscache.path, 0o777)
 
-    def getAllPlugins(self):
+    def getAllPlugins(self) -> list[str]:
         """
         Get all the plugins loadable from our dummy package, and return their
         short names.
@@ -461,9 +474,9 @@ def getAllPlugins(self):
         import plugindummy.plugins  # type: ignore[import]
 
         x = list(plugin.getPlugins(ITestPlugin, plugindummy.plugins))
-        return [plug.__name__ for plug in x]
+        return [plug.__name__ for plug in x]  # type: ignore[attr-defined]
 
-    def resetEnvironment(self):
+    def resetEnvironment(self) -> None:
         """
         Change the environment to what it should be just as the test is
         starting.
@@ -471,7 +484,7 @@ def resetEnvironment(self):
         self.unsetEnvironment()
         sys.path.extend([x.path for x in [self.devPath, self.systemPath, self.appPath]])
 
-    def unsetEnvironment(self):
+    def unsetEnvironment(self) -> None:
         """
         Change the Python environment back to what it was before the test was
         started.
@@ -481,7 +494,7 @@ def unsetEnvironment(self):
         sys.modules.update(self.savedModules)
         sys.path[:] = self.savedPath
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         """
         Reset the Python environment to what it was before this test ran, and
         restore permissions on files which were marked read-only so that the
@@ -494,7 +507,7 @@ def tearDown(self):
         # couldn't delete, and that would be bad.
         self.unlockSystem()
 
-    def test_developmentPluginAvailability(self):
+    def test_developmentPluginAvailability(self) -> None:
         """
         Plugins added in the development path should be loadable, even when
         the (now non-importable) system path contains its own idea of the
@@ -508,7 +521,7 @@ def test_developmentPluginAvailability(self):
             names.sort()
             self.assertEqual(names, ["app", "dev"])
 
-    def test_freshPyReplacesStalePyc(self):
+    def test_freshPyReplacesStalePyc(self) -> None:
         """
         Verify that if a stale .pyc file on the PYTHONPATH is replaced by a
         fresh .py file, the plugins in the new .py are picked up rather than
@@ -525,7 +538,7 @@ def test_freshPyReplacesStalePyc(self):
         # of scanning for .pyc files is for configurations where you want
         # to intentionally include them, which means we _don't_ scan for
         # them inside cache directories.
-        extra = dict(legacy=True)
+        extra = _HasBoolLegacyKey(legacy=True)
         compileall.compile_dir(self.appPackage.path, quiet=1, **extra)
         os.utime(pyc.path, (x, x))
         # Eliminate the other option.
@@ -540,7 +553,7 @@ def test_freshPyReplacesStalePyc(self):
         self.failIfIn("one", self.getAllPlugins())
         self.assertIn("two", self.getAllPlugins())
 
-    def test_newPluginsOnReadOnlyPath(self):
+    def test_newPluginsOnReadOnlyPath(self) -> None:
         """
         Verify that a failure to write the dropin.cache file on a read-only
         path will not affect the list of plugins returned.
@@ -558,7 +571,7 @@ def test_newPluginsOnReadOnlyPath(self):
         sys.path.remove(self.devPath.path)
 
         # Start observing log events to see the warning
-        events = []
+        events: list[EventDict] = []
         addObserver(events.append)
         self.addCleanup(removeObserver, events.append)
 
@@ -570,9 +583,11 @@ def test_newPluginsOnReadOnlyPath(self):
             errno.EPERM,
         )
         for event in events:
-            if expected in textFromEventDict(event):
+            maybeText = textFromEventDict(event)
+            assert maybeText is not None
+            if expected in maybeText:  # pragma: no branch
                 break
-        else:
+        else:  # pragma: no cover
             self.fail(
                 "Did not observe unwriteable cache warning in log "
                 "events: %r" % (events,)
@@ -585,14 +600,14 @@ class AdjacentPackageTests(unittest.TestCase):
     installed copies of the package containing the plugins being loaded.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Save the elements of C{sys.path} and the items of C{sys.modules}.
         """
         self.originalPath = sys.path[:]
         self.savedModules = sys.modules.copy()
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         """
         Restore C{sys.path} and C{sys.modules} to their original values.
         """
@@ -600,7 +615,9 @@ def tearDown(self):
         sys.modules.clear()
         sys.modules.update(self.savedModules)
 
-    def createDummyPackage(self, root, name, pluginName):
+    def createDummyPackage(
+        self, root: FilePath[str], name: str, pluginName: str
+    ) -> FilePath[str]:
         """
         Create a directory containing a Python package named I{dummy} with a
         I{plugins} subpackage.
@@ -631,7 +648,7 @@ def createDummyPackage(self, root, name, pluginName):
         pluginModule.setContent(pluginFileContents(name))
         return directory
 
-    def test_hiddenPackageSamePluginModuleNameObscured(self):
+    def test_hiddenPackageSamePluginModuleNameObscured(self) -> None:
         """
         Only plugins from the first package in sys.path should be returned by
         getPlugins in the case where there are two Python packages by the same
@@ -649,9 +666,9 @@ def test_hiddenPackageSamePluginModuleNameObscured(self):
         import dummy.plugins  # type: ignore[import]
 
         plugins = list(plugin.getPlugins(ITestPlugin, dummy.plugins))
-        self.assertEqual(["first"], [p.__name__ for p in plugins])
+        self.assertEqual(["first"], [p.__name__ for p in plugins])  # type: ignore[attr-defined]
 
-    def test_hiddenPackageDifferentPluginModuleNameObscured(self):
+    def test_hiddenPackageDifferentPluginModuleNameObscured(self) -> None:
         """
         Plugins from the first package in sys.path should be returned by
         getPlugins in the case where there are two Python packages by the same
@@ -669,7 +686,7 @@ def test_hiddenPackageDifferentPluginModuleNameObscured(self):
         import dummy.plugins
 
         plugins = list(plugin.getPlugins(ITestPlugin, dummy.plugins))
-        self.assertEqual(["first"], [p.__name__ for p in plugins])
+        self.assertEqual(["first"], [p.__name__ for p in plugins])  # type: ignore[attr-defined]
 
 
 class PackagePathTests(unittest.TestCase):
@@ -678,19 +695,19 @@ class PackagePathTests(unittest.TestCase):
     plugin packages.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Save the elements of C{sys.path}.
         """
         self.originalPath = sys.path[:]
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         """
         Restore C{sys.path} to its original value.
         """
         sys.path[:] = self.originalPath
 
-    def test_pluginDirectories(self):
+    def test_pluginDirectories(self) -> None:
         """
         L{plugin.pluginPackagePaths} should return a list containing each
         directory in C{sys.path} with a suffix based on the supplied package
@@ -707,7 +724,7 @@ def test_pluginDirectories(self):
             ],
         )
 
-    def test_pluginPackagesExcluded(self):
+    def test_pluginPackagesExcluded(self) -> None:
         """
         L{plugin.pluginPackagePaths} should exclude directories which are
         Python packages.  The only allowed plugin package (the only one
diff --git a/src/twisted/test/test_randbytes.py b/src/twisted/test/test_randbytes.py
--- a/src/twisted/test/test_randbytes.py
+++ b/src/twisted/test/test_randbytes.py
@@ -4,18 +4,30 @@
 """
 Test cases for L{twisted.python.randbytes}.
 """
+from __future__ import annotations
 
+from typing import Callable
+
+from typing_extensions import NoReturn, Protocol
 
 from twisted.python import randbytes
 from twisted.trial import unittest
 
 
+class _SupportsAssertions(Protocol):
+    def assertEqual(self, a: object, b: object) -> object:
+        ...
+
+    def assertNotEqual(self, a: object, b: object) -> object:
+        ...
+
+
 class SecureRandomTestCaseBase:
     """
     Base class for secureRandom test cases.
     """
 
-    def _check(self, source):
+    def _check(self: _SupportsAssertions, source: Callable[[int], bytes]) -> None:
         """
         The given random bytes source should return the number of bytes
         requested each time it is called and should probably not return the
@@ -37,7 +49,7 @@ class SecureRandomTests(SecureRandomTestCaseBase, unittest.TestCase):
     Test secureRandom under normal conditions.
     """
 
-    def test_normal(self):
+    def test_normal(self) -> None:
         """
         L{randbytes.secureRandom} should return a string of the requested
         length and make some effort to make its result otherwise unpredictable.
@@ -52,36 +64,36 @@ class ConditionalSecureRandomTests(
     Test random sources one by one, then remove it to.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Create a L{randbytes.RandomFactory} to use in the tests.
         """
         self.factory = randbytes.RandomFactory()
 
-    def errorFactory(self, nbytes):
+    def errorFactory(self, nbytes: object) -> NoReturn:
         """
         A factory raising an error when a source is not available.
         """
         raise randbytes.SourceNotAvailable()
 
-    def test_osUrandom(self):
+    def test_osUrandom(self) -> None:
         """
         L{RandomFactory._osUrandom} should work as a random source whenever
         L{os.urandom} is available.
         """
         self._check(self.factory._osUrandom)
 
-    def test_withoutAnything(self):
+    def test_withoutAnything(self) -> None:
         """
         Remove all secure sources and assert it raises a failure. Then try the
         fallback parameter.
         """
-        self.factory._osUrandom = self.errorFactory
+        self.factory._osUrandom = self.errorFactory  # type: ignore[method-assign]
         self.assertRaises(
             randbytes.SecureRandomNotAvailable, self.factory.secureRandom, 18
         )
 
-        def wrapper():
+        def wrapper() -> bytes:
             return self.factory.secureRandom(18, fallback=True)
 
         s = self.assertWarns(
@@ -99,13 +111,13 @@ class RandomBaseTests(SecureRandomTestCaseBase, unittest.SynchronousTestCase):
     'Normal' random test cases.
     """
 
-    def test_normal(self):
+    def test_normal(self) -> None:
         """
         Test basic case.
         """
         self._check(randbytes.insecureRandom)
 
-    def test_withoutGetrandbits(self):
+    def test_withoutGetrandbits(self) -> None:
         """
         Test C{insecureRandom} without C{random.getrandbits}.
         """
diff --git a/src/twisted/test/test_tpfile.py b/src/twisted/test/test_tpfile.py
--- a/src/twisted/test/test_tpfile.py
+++ b/src/twisted/test/test_tpfile.py
@@ -4,7 +4,7 @@
 
 from io import BytesIO
 
-from twisted.internet import abstract, protocol
+from twisted.internet import abstract, defer, protocol
 from twisted.protocols import basic, loopback
 from twisted.trial import unittest
 
@@ -12,31 +12,35 @@
 class BufferingServer(protocol.Protocol):
     buffer = b""
 
-    def dataReceived(self, data):
+    def dataReceived(self, data: bytes) -> None:
         self.buffer += data
 
 
 class FileSendingClient(protocol.Protocol):
-    def __init__(self, f):
+    def __init__(self, f: BytesIO) -> None:
         self.f = f
 
-    def connectionMade(self):
+    def connectionMade(self) -> None:
+        assert self.transport is not None
         s = basic.FileSender()
         d = s.beginFileTransfer(self.f, self.transport, lambda x: x)
         d.addCallback(lambda r: self.transport.loseConnection())
 
 
 class FileSenderTests(unittest.TestCase):
-    def testSendingFile(self):
+    def testSendingFile(self) -> defer.Deferred[None]:
         testStr = b"xyz" * 100 + b"abc" * 100 + b"123" * 100
         s = BufferingServer()
         c = FileSendingClient(BytesIO(testStr))
 
-        d = loopback.loopbackTCP(s, c)
-        d.addCallback(lambda x: self.assertEqual(s.buffer, testStr))
-        return d
+        d: defer.Deferred[None] = loopback.loopbackTCP(s, c)
 
-    def testSendingEmptyFile(self):
+        def callback(x: object) -> None:
+            self.assertEqual(s.buffer, testStr)
+
+        return d.addCallback(callback)
+
+    def testSendingEmptyFile(self) -> None:
         fileSender = basic.FileSender()
         consumer = abstract.FileDescriptor()
         consumer.connected = 1
diff --git a/src/twisted/trial/test/detests.py b/src/twisted/trial/test/detests.py
--- a/src/twisted/trial/test/detests.py
+++ b/src/twisted/trial/test/detests.py
@@ -4,9 +4,10 @@
 """
 Tests for Deferred handling by L{twisted.trial.unittest.TestCase}.
 """
-
+from __future__ import annotations
 
 from twisted.internet import defer, reactor, threads
+from twisted.python.failure import Failure
 from twisted.python.util import runWithWarningsSuppressed
 from twisted.trial import unittest
 from twisted.trial.util import suppress as SUPPRESS
@@ -160,7 +161,7 @@ def test_expectedFailure(self):
 
 
 class TimeoutTests(unittest.TestCase):
-    timedOut = None
+    timedOut: Failure | None = None
 
     def test_pass(self):
         d = defer.Deferred()
diff --git a/src/twisted/trial/test/test_deferred.py b/src/twisted/trial/test/test_deferred.py
--- a/src/twisted/trial/test/test_deferred.py
+++ b/src/twisted/trial/test/test_deferred.py
@@ -4,29 +4,32 @@
 """
 Tests for returning Deferreds from a TestCase.
 """
-
+from __future__ import annotations
 
 import unittest as pyunit
 
 from twisted.internet import defer
+from twisted.python.failure import Failure
 from twisted.trial import reporter, unittest, util
 from twisted.trial.test import detests
 
 
 class SetUpTests(unittest.TestCase):
-    def _loadSuite(self, klass):
+    def _loadSuite(
+        self, klass: type[pyunit.TestCase]
+    ) -> tuple[reporter.TestResult, pyunit.TestSuite]:
         loader = pyunit.TestLoader()
         r = reporter.TestResult()
         s = loader.loadTestsFromTestCase(klass)
         return r, s
 
-    def test_success(self):
+    def test_success(self) -> None:
         result, suite = self._loadSuite(detests.DeferredSetUpOK)
         suite(result)
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
 
-    def test_fail(self):
+    def test_fail(self) -> None:
         self.assertFalse(detests.DeferredSetUpFail.testCalled)
         result, suite = self._loadSuite(detests.DeferredSetUpFail)
         suite(result)
@@ -36,7 +39,7 @@ def test_fail(self):
         self.assertEqual(len(result.errors), 1)
         self.assertFalse(detests.DeferredSetUpFail.testCalled)
 
-    def test_callbackFail(self):
+    def test_callbackFail(self) -> None:
         self.assertFalse(detests.DeferredSetUpCallbackFail.testCalled)
         result, suite = self._loadSuite(detests.DeferredSetUpCallbackFail)
         suite(result)
@@ -46,7 +49,7 @@ def test_callbackFail(self):
         self.assertEqual(len(result.errors), 1)
         self.assertFalse(detests.DeferredSetUpCallbackFail.testCalled)
 
-    def test_error(self):
+    def test_error(self) -> None:
         self.assertFalse(detests.DeferredSetUpError.testCalled)
         result, suite = self._loadSuite(detests.DeferredSetUpError)
         suite(result)
@@ -56,7 +59,7 @@ def test_error(self):
         self.assertEqual(len(result.errors), 1)
         self.assertFalse(detests.DeferredSetUpError.testCalled)
 
-    def test_skip(self):
+    def test_skip(self) -> None:
         self.assertFalse(detests.DeferredSetUpSkip.testCalled)
         result, suite = self._loadSuite(detests.DeferredSetUpSkip)
         suite(result)
@@ -69,20 +72,22 @@ def test_skip(self):
 
 
 class NeverFireTests(unittest.TestCase):
-    def setUp(self):
+    def setUp(self) -> None:
         self._oldTimeout = util.DEFAULT_TIMEOUT_DURATION
         util.DEFAULT_TIMEOUT_DURATION = 0.1
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         util.DEFAULT_TIMEOUT_DURATION = self._oldTimeout
 
-    def _loadSuite(self, klass):
+    def _loadSuite(
+        self, klass: type[pyunit.TestCase]
+    ) -> tuple[reporter.TestResult, pyunit.TestSuite]:
         loader = pyunit.TestLoader()
         r = reporter.TestResult()
         s = loader.loadTestsFromTestCase(klass)
         return r, s
 
-    def test_setUp(self):
+    def test_setUp(self) -> None:
         self.assertFalse(detests.DeferredSetUpNeverFire.testCalled)
         result, suite = self._loadSuite(detests.DeferredSetUpNeverFire)
         suite(result)
@@ -91,29 +96,30 @@ def test_setUp(self):
         self.assertEqual(len(result.failures), 0)
         self.assertEqual(len(result.errors), 1)
         self.assertFalse(detests.DeferredSetUpNeverFire.testCalled)
+        assert isinstance(result.errors[0][1], Failure)
         self.assertTrue(result.errors[0][1].check(defer.TimeoutError))
 
 
 class TestTester(unittest.TestCase):
-    def getTest(self, name):
+    def getTest(self, name: str) -> pyunit.TestCase:
         raise NotImplementedError("must override me")
 
-    def runTest(self, name):
+    def runTest(self, name: str) -> reporter.TestResult:  # type: ignore[override]
         result = reporter.TestResult()
         self.getTest(name).run(result)
         return result
 
 
 class DeferredTests(TestTester):
-    def getTest(self, name):
+    def getTest(self, name: str) -> detests.DeferredTests:
         return detests.DeferredTests(name)
 
-    def test_pass(self):
+    def test_pass(self) -> None:
         result = self.runTest("test_pass")
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
 
-    def test_passGenerated(self):
+    def test_passGenerated(self) -> None:
         result = self.runTest("test_passGenerated")
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
@@ -123,7 +129,7 @@ def test_passGenerated(self):
         util.suppress(message="twisted.internet.defer.deferredGenerator is deprecated")
     ]
 
-    def test_passInlineCallbacks(self):
+    def test_passInlineCallbacks(self) -> None:
         """
         The body of a L{defer.inlineCallbacks} decorated test gets run.
         """
@@ -132,32 +138,32 @@ def test_passInlineCallbacks(self):
         self.assertEqual(result.testsRun, 1)
         self.assertTrue(detests.DeferredTests.touched)
 
-    def test_fail(self):
+    def test_fail(self) -> None:
         result = self.runTest("test_fail")
         self.assertFalse(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.failures), 1)
 
-    def test_failureInCallback(self):
+    def test_failureInCallback(self) -> None:
         result = self.runTest("test_failureInCallback")
         self.assertFalse(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.failures), 1)
 
-    def test_errorInCallback(self):
+    def test_errorInCallback(self) -> None:
         result = self.runTest("test_errorInCallback")
         self.assertFalse(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.errors), 1)
 
-    def test_skip(self):
+    def test_skip(self) -> None:
         result = self.runTest("test_skip")
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.skips), 1)
         self.assertFalse(detests.DeferredTests.touched)
 
-    def test_todo(self):
+    def test_todo(self) -> None:
         result = self.runTest("test_expectedFailure")
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
@@ -165,80 +171,86 @@ def test_todo(self):
         self.assertEqual(len(result.failures), 0)
         self.assertEqual(len(result.expectedFailures), 1)
 
-    def test_thread(self):
+    def test_thread(self) -> None:
         result = self.runTest("test_thread")
         self.assertEqual(result.testsRun, 1)
         self.assertTrue(result.wasSuccessful(), result.errors)
 
 
 class TimeoutTests(TestTester):
-    def getTest(self, name):
+    def getTest(self, name: str) -> detests.TimeoutTests:
         return detests.TimeoutTests(name)
 
-    def _wasTimeout(self, error):
+    def _wasTimeout(self, error: Failure) -> None:
         self.assertEqual(error.check(defer.TimeoutError), defer.TimeoutError)
 
-    def test_pass(self):
+    def test_pass(self) -> None:
         result = self.runTest("test_pass")
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
 
-    def test_passDefault(self):
+    def test_passDefault(self) -> None:
         result = self.runTest("test_passDefault")
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
 
-    def test_timeout(self):
+    def test_timeout(self) -> None:
         result = self.runTest("test_timeout")
         self.assertFalse(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.errors), 1)
+        assert isinstance(result.errors[0][1], Failure)
         self._wasTimeout(result.errors[0][1])
 
-    def test_timeoutZero(self):
+    def test_timeoutZero(self) -> None:
         result = self.runTest("test_timeoutZero")
         self.assertFalse(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.errors), 1)
+        assert isinstance(result.errors[0][1], Failure)
         self._wasTimeout(result.errors[0][1])
 
-    def test_skip(self):
+    def test_skip(self) -> None:
         result = self.runTest("test_skip")
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.skips), 1)
 
-    def test_todo(self):
+    def test_todo(self) -> None:
         result = self.runTest("test_expectedFailure")
         self.assertTrue(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.expectedFailures), 1)
+        assert isinstance(result.expectedFailures[0][1], Failure)
         self._wasTimeout(result.expectedFailures[0][1])
 
-    def test_errorPropagation(self):
+    def test_errorPropagation(self) -> None:
         result = self.runTest("test_errorPropagation")
         self.assertFalse(result.wasSuccessful())
         self.assertEqual(result.testsRun, 1)
+        assert detests.TimeoutTests.timedOut is not None
         self._wasTimeout(detests.TimeoutTests.timedOut)
 
-    def test_classTimeout(self):
+    def test_classTimeout(self) -> None:
         loader = pyunit.TestLoader()
         suite = loader.loadTestsFromTestCase(detests.TestClassTimeoutAttribute)
         result = reporter.TestResult()
         suite.run(result)
         self.assertEqual(len(result.errors), 1)
+        assert isinstance(result.errors[0][1], Failure)
         self._wasTimeout(result.errors[0][1])
 
-    def test_callbackReturnsNonCallingDeferred(self):
+    def test_callbackReturnsNonCallingDeferred(self) -> None:
         # hacky timeout
         # raises KeyboardInterrupt because Trial sucks
         from twisted.internet import reactor
 
-        call = reactor.callLater(2, reactor.crash)
+        call = reactor.callLater(2, reactor.crash)  # type: ignore[attr-defined]
         result = self.runTest("test_calledButNeverCallback")
         if call.active():
             call.cancel()
         self.assertFalse(result.wasSuccessful())
+        assert isinstance(result.errors[0][1], Failure)
         self._wasTimeout(result.errors[0][1])
 
 
diff --git a/src/twisted/trial/test/test_keyboard.py b/src/twisted/trial/test/test_keyboard.py
--- a/src/twisted/trial/test/test_keyboard.py
+++ b/src/twisted/trial/test/test_keyboard.py
@@ -4,7 +4,7 @@
 """
 Tests for interrupting tests with Control-C.
 """
-
+from __future__ import annotations
 
 from io import StringIO
 
@@ -12,34 +12,36 @@
 
 
 class TrialTest(unittest.SynchronousTestCase):
-    def setUp(self):
+    def setUp(self) -> None:
         self.output = StringIO()
         self.reporter = reporter.TestResult()
         self.loader = runner.TestLoader()
 
 
 class InterruptInTestTests(TrialTest):
+    test_03_doNothing_run: bool | None
+
     class InterruptedTest(unittest.TestCase):
-        def test_02_raiseInterrupt(self):
+        def test_02_raiseInterrupt(self) -> None:
             raise KeyboardInterrupt
 
-        def test_01_doNothing(self):
+        def test_01_doNothing(self) -> None:
             pass
 
-        def test_03_doNothing(self):
+        def test_03_doNothing(self) -> None:
             InterruptInTestTests.test_03_doNothing_run = True
 
-    def setUp(self):
+    def setUp(self) -> None:
         super().setUp()
         self.suite = self.loader.loadClass(InterruptInTestTests.InterruptedTest)
         InterruptInTestTests.test_03_doNothing_run = None
 
-    def test_setUpOK(self):
+    def test_setUpOK(self) -> None:
         self.assertEqual(3, self.suite.countTestCases())
         self.assertEqual(0, self.reporter.testsRun)
         self.assertFalse(self.reporter.shouldStop)
 
-    def test_interruptInTest(self):
+    def test_interruptInTest(self) -> None:
         runner.TrialSuite([self.suite]).run(self.reporter)
         self.assertTrue(self.reporter.shouldStop)
         self.assertEqual(2, self.reporter.testsRun)
@@ -50,32 +52,33 @@ def test_interruptInTest(self):
 
 class InterruptInSetUpTests(TrialTest):
     testsRun = 0
+    test_02_run: bool
 
     class InterruptedTest(unittest.TestCase):
-        def setUp(self):
+        def setUp(self) -> None:
             if InterruptInSetUpTests.testsRun > 0:
                 raise KeyboardInterrupt
 
-        def test_01(self):
+        def test_01(self) -> None:
             InterruptInSetUpTests.testsRun += 1
 
-        def test_02(self):
+        def test_02(self) -> None:
             InterruptInSetUpTests.testsRun += 1
             InterruptInSetUpTests.test_02_run = True
 
-    def setUp(self):
+    def setUp(self) -> None:
         super().setUp()
         self.suite = self.loader.loadClass(InterruptInSetUpTests.InterruptedTest)
         InterruptInSetUpTests.test_02_run = False
         InterruptInSetUpTests.testsRun = 0
 
-    def test_setUpOK(self):
+    def test_setUpOK(self) -> None:
         self.assertEqual(0, InterruptInSetUpTests.testsRun)
         self.assertEqual(2, self.suite.countTestCases())
         self.assertEqual(0, self.reporter.testsRun)
         self.assertFalse(self.reporter.shouldStop)
 
-    def test_interruptInSetUp(self):
+    def test_interruptInSetUp(self) -> None:
         runner.TrialSuite([self.suite]).run(self.reporter)
         self.assertTrue(self.reporter.shouldStop)
         self.assertEqual(2, self.reporter.testsRun)
@@ -84,32 +87,33 @@ def test_interruptInSetUp(self):
 
 class InterruptInTearDownTests(TrialTest):
     testsRun = 0
+    test_02_run: bool
 
     class InterruptedTest(unittest.TestCase):
-        def tearDown(self):
+        def tearDown(self) -> None:
             if InterruptInTearDownTests.testsRun > 0:
                 raise KeyboardInterrupt
 
-        def test_01(self):
+        def test_01(self) -> None:
             InterruptInTearDownTests.testsRun += 1
 
-        def test_02(self):
+        def test_02(self) -> None:
             InterruptInTearDownTests.testsRun += 1
             InterruptInTearDownTests.test_02_run = True
 
-    def setUp(self):
+    def setUp(self) -> None:
         super().setUp()
         self.suite = self.loader.loadClass(InterruptInTearDownTests.InterruptedTest)
         InterruptInTearDownTests.testsRun = 0
         InterruptInTearDownTests.test_02_run = False
 
-    def test_setUpOK(self):
+    def test_setUpOK(self) -> None:
         self.assertEqual(0, InterruptInTearDownTests.testsRun)
         self.assertEqual(2, self.suite.countTestCases())
         self.assertEqual(0, self.reporter.testsRun)
         self.assertFalse(self.reporter.shouldStop)
 
-    def test_interruptInTearDown(self):
+    def test_interruptInTearDown(self) -> None:
         runner.TrialSuite([self.suite]).run(self.reporter)
         self.assertEqual(1, self.reporter.testsRun)
         self.assertTrue(self.reporter.shouldStop)
diff --git a/src/twisted/trial/test/test_loader.py b/src/twisted/trial/test/test_loader.py
--- a/src/twisted/trial/test/test_loader.py
+++ b/src/twisted/trial/test/test_loader.py
@@ -4,7 +4,7 @@
 """
 Tests for loading tests by name.
 """
-
+from __future__ import annotations
 
 import os
 import sys
@@ -12,12 +12,13 @@
 from hashlib import md5
 from operator import attrgetter
 from types import ModuleType
+from typing import TYPE_CHECKING, Callable, Generator
 
 from hamcrest import assert_that, equal_to, has_properties
 from hamcrest.core.matcher import Matcher
 
 from twisted.python import filepath, util
-from twisted.python.modules import getModule
+from twisted.python.modules import PythonAttribute, PythonModule, getModule
 from twisted.python.reflect import ModuleNotFound
 from twisted.trial import reporter, runner, unittest
 from twisted.trial._asyncrunner import _iterateTests
@@ -25,8 +26,11 @@
 from twisted.trial.test import packages
 from .matchers import after
 
+if TYPE_CHECKING:
+    from _typeshed import SupportsRichComparison
+
 
-def testNames(tests):
+def testNames(tests: pyunit.TestCase | pyunit.TestSuite) -> list[str]:
     """
     Return the id of each test within the given test suite or case.
     """
@@ -37,11 +41,11 @@ def testNames(tests):
 
 
 class FinderPy3Tests(packages.SysPathManglingTest):
-    def setUp(self):
+    def setUp(self) -> None:  # type: ignore[override]
         super().setUp()
         self.loader = runner.TestLoader()
 
-    def test_findNonModule(self):
+    def test_findNonModule(self) -> None:
         """
         findByName, if given something findable up until the last entry, will
         raise AttributeError (as it cannot tell if 'nonexistent' here is
@@ -51,10 +55,10 @@ def test_findNonModule(self):
             AttributeError, self.loader.findByName, "twisted.trial.test.nonexistent"
         )
 
-    def test_findNonPackage(self):
+    def test_findNonPackage(self) -> None:
         self.assertRaises(ModuleNotFound, self.loader.findByName, "nonextant")
 
-    def test_findNonFile(self):
+    def test_findNonFile(self) -> None:
         """
         findByName, given a file path that doesn't exist, will raise a
         ValueError saying that it is not a Python file.
@@ -62,7 +66,7 @@ def test_findNonFile(self):
         path = util.sibpath(__file__, "nonexistent.py")
         self.assertRaises(ValueError, self.loader.findByName, path)
 
-    def test_findFileWithImportError(self):
+    def test_findFileWithImportError(self) -> None:
         """
         findByName will re-raise ImportErrors inside modules that it has found
         and imported.
@@ -101,7 +105,7 @@ class FileTests(packages.SysPathManglingTest):
     Tests for L{runner.filenameToModule}.
     """
 
-    def test_notFile(self):
+    def test_notFile(self) -> None:
         """
         L{runner.filenameToModule} raises a C{ValueError} when a non-existing
         file is passed.
@@ -109,7 +113,7 @@ def test_notFile(self):
         err = self.assertRaises(ValueError, runner.filenameToModule, "it")
         self.assertEqual(str(err), "'it' doesn't exist")
 
-    def test_moduleInPath(self):
+    def test_moduleInPath(self) -> None:
         """
         If the file in question is a module on the Python path, then it should
         properly import and return that module.
@@ -140,7 +144,7 @@ def test_moduleNotInPath(self) -> None:
         self.assertIsNot(sample1, sample2)
         assert_that(sample1, looselyResembles(sample2))
 
-    def test_packageInPath(self):
+    def test_packageInPath(self) -> None:
         """
         If the file in question is a package on the Python path, then it should
         properly import and return that package.
@@ -167,7 +171,7 @@ def test_packageNotInPath(self) -> None:
         self.assertIsNot(package1, goodpackage)
         assert_that(package1, looselyResembles(goodpackage))
 
-    def test_directoryNotPackage(self):
+    def test_directoryNotPackage(self) -> None:
         """
         L{runner.filenameToModule} raises a C{ValueError} when the name of an
         empty directory is passed that isn't considered a valid Python package
@@ -179,7 +183,7 @@ def test_directoryNotPackage(self):
         err = self.assertRaises(ValueError, runner.filenameToModule, emptyDir.path)
         self.assertEqual(str(err), f"{emptyDir.path!r} is not a package directory")
 
-    def test_filenameNotPython(self):
+    def test_filenameNotPython(self) -> None:
         """
         L{runner.filenameToModule} raises a C{SyntaxError} when a non-Python
         file is passed.
@@ -188,7 +192,7 @@ def test_filenameNotPython(self):
         filename.setContent(b"This isn't python")
         self.assertRaises(SyntaxError, runner.filenameToModule, filename.path)
 
-    def test_filenameMatchesPackage(self):
+    def test_filenameMatchesPackage(self) -> None:
         """
         The C{__file__} attribute of the module should match the package name.
         """
@@ -201,7 +205,7 @@ def test_filenameMatchesPackage(self):
         finally:
             filename.remove()
 
-    def test_directory(self):
+    def test_directory(self) -> None:
         """
         Test loader against a filesystem directory containing an empty
         C{__init__.py} file. It should handle 'path' and 'path/' the same way.
@@ -224,11 +228,11 @@ class LoaderTests(packages.SysPathManglingTest):
     Tests for L{trial.TestLoader}.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:  # type: ignore[override]
         self.loader = runner.TestLoader()
         packages.SysPathManglingTest.setUp(self)
 
-    def test_sortCases(self):
+    def test_sortCases(self) -> None:
         from twisted.trial.test import sample
 
         suite = self.loader.loadClass(sample.AlphabetTest)
@@ -238,11 +242,11 @@ def test_sortCases(self):
         )
         newOrder = ["test_b", "test_c", "test_a"]
         sortDict = dict(zip(newOrder, range(3)))
-        self.loader.sorter = lambda x: sortDict.get(x.shortDescription(), -1)
+        self.loader.sorter = lambda x: sortDict.get(x.shortDescription(), -1)  # type: ignore[arg-type, union-attr, call-arg]
         suite = self.loader.loadClass(sample.AlphabetTest)
         self.assertEqual(newOrder, [test._testMethodName for test in suite._tests])
 
-    def test_loadFailure(self):
+    def test_loadFailure(self) -> None:
         """
         Loading a test that fails and getting the result of it ends up with one
         test ran and one failure.
@@ -255,7 +259,7 @@ def test_loadFailure(self):
         self.assertEqual(result.testsRun, 1)
         self.assertEqual(len(result.failures), 1)
 
-    def test_loadBadDecorator(self):
+    def test_loadBadDecorator(self) -> None:
         """
         A decorated test method for which the decorator has failed to set the
         method's __name__ correctly is loaded and its name in the class scope
@@ -271,7 +275,7 @@ def test_loadBadDecorator(self):
         self.assertEqual(1, suite.countTestCases())
         self.assertEqual("test_badDecorator", suite._testMethodName)
 
-    def test_loadGoodDecorator(self):
+    def test_loadGoodDecorator(self) -> None:
         """
         A decorated test method for which the decorator has set the method's
         __name__ correctly is loaded and the only name by which it goes is used.
@@ -286,7 +290,7 @@ def test_loadGoodDecorator(self):
         self.assertEqual(1, suite.countTestCases())
         self.assertEqual("test_goodDecorator", suite._testMethodName)
 
-    def test_loadRenamedDecorator(self):
+    def test_loadRenamedDecorator(self) -> None:
         """
         Load a decorated method which has been copied to a new name inside the
         class.  Thus its __name__ and its key in the class's __dict__ no
@@ -302,7 +306,7 @@ def test_loadRenamedDecorator(self):
         self.assertEqual(1, suite.countTestCases())
         self.assertEqual("test_renamedDecorator", suite._testMethodName)
 
-    def test_loadClass(self):
+    def test_loadClass(self) -> None:
         from twisted.trial.test import sample
 
         suite = self.loader.loadClass(sample.FooTest)
@@ -311,7 +315,7 @@ def test_loadClass(self):
             ["test_bar", "test_foo"], [test._testMethodName for test in suite._tests]
         )
 
-    def test_loadNonClass(self):
+    def test_loadNonClass(self) -> None:
         from twisted.trial.test import sample
 
         self.assertRaises(TypeError, self.loader.loadClass, sample)
@@ -319,18 +323,18 @@ def test_loadNonClass(self):
         self.assertRaises(TypeError, self.loader.loadClass, "string")
         self.assertRaises(TypeError, self.loader.loadClass, ("foo", "bar"))
 
-    def test_loadNonTestCase(self):
+    def test_loadNonTestCase(self) -> None:
         from twisted.trial.test import sample
 
         self.assertRaises(ValueError, self.loader.loadClass, sample.NotATest)
 
-    def test_loadModule(self):
+    def test_loadModule(self) -> None:
         from twisted.trial.test import sample
 
         suite = self.loader.loadModule(sample)
         self.assertEqual(10, suite.countTestCases())
 
-    def test_loadNonModule(self):
+    def test_loadNonModule(self) -> None:
         from twisted.trial.test import sample
 
         self.assertRaises(TypeError, self.loader.loadModule, sample.FooTest)
@@ -338,13 +342,13 @@ def test_loadNonModule(self):
         self.assertRaises(TypeError, self.loader.loadModule, "string")
         self.assertRaises(TypeError, self.loader.loadModule, ("foo", "bar"))
 
-    def test_loadPackage(self):
+    def test_loadPackage(self) -> None:
         import goodpackage
 
         suite = self.loader.loadPackage(goodpackage)
         self.assertEqual(7, suite.countTestCases())
 
-    def test_loadNonPackage(self):
+    def test_loadNonPackage(self) -> None:
         from twisted.trial.test import sample
 
         self.assertRaises(TypeError, self.loader.loadPackage, sample.FooTest)
@@ -352,19 +356,19 @@ def test_loadNonPackage(self):
         self.assertRaises(TypeError, self.loader.loadPackage, "string")
         self.assertRaises(TypeError, self.loader.loadPackage, ("foo", "bar"))
 
-    def test_loadModuleAsPackage(self):
+    def test_loadModuleAsPackage(self) -> None:
         from twisted.trial.test import sample
 
         ## XXX -- should this instead raise a ValueError? -- jml
         self.assertRaises(TypeError, self.loader.loadPackage, sample)
 
-    def test_loadPackageRecursive(self):
+    def test_loadPackageRecursive(self) -> None:
         import goodpackage
 
         suite = self.loader.loadPackage(goodpackage, recurse=True)
         self.assertEqual(14, suite.countTestCases())
 
-    def test_loadAnythingOnModule(self):
+    def test_loadAnythingOnModule(self) -> None:
         from twisted.trial.test import sample
 
         suite = self.loader.loadAnything(sample)
@@ -372,32 +376,32 @@ def test_loadAnythingOnModule(self):
             sample.__name__, suite._tests[0]._tests[0].__class__.__module__
         )
 
-    def test_loadAnythingOnClass(self):
+    def test_loadAnythingOnClass(self) -> None:
         from twisted.trial.test import sample
 
         suite = self.loader.loadAnything(sample.FooTest)
         self.assertEqual(2, suite.countTestCases())
 
-    def test_loadAnythingOnPackage(self):
+    def test_loadAnythingOnPackage(self) -> None:
         import goodpackage
 
         suite = self.loader.loadAnything(goodpackage)
         self.assertTrue(isinstance(suite, self.loader.suiteFactory))
         self.assertEqual(7, suite.countTestCases())
 
-    def test_loadAnythingOnPackageRecursive(self):
+    def test_loadAnythingOnPackageRecursive(self) -> None:
         import goodpackage
 
         suite = self.loader.loadAnything(goodpackage, recurse=True)
         self.assertTrue(isinstance(suite, self.loader.suiteFactory))
         self.assertEqual(14, suite.countTestCases())
 
-    def test_loadAnythingOnString(self):
+    def test_loadAnythingOnString(self) -> None:
         # the important thing about this test is not the string-iness
         # but the non-handledness.
         self.assertRaises(TypeError, self.loader.loadAnything, "goodpackage")
 
-    def test_importErrors(self):
+    def test_importErrors(self) -> None:
         import package  # type: ignore[import]
 
         suite = self.loader.loadPackage(package, recurse=True)
@@ -411,7 +415,7 @@ def test_importErrors(self):
             errors, ["package.test_bad_module", "package.test_import_module"]
         )
 
-    def test_differentInstances(self):
+    def test_differentInstances(self) -> None:
         """
         L{TestLoader.loadClass} returns a suite with each test method
         represented by a different instances of the L{TestCase} they are
@@ -419,10 +423,10 @@ def test_differentInstances(self):
         """
 
         class DistinctInstances(pyunit.TestCase):
-            def test_1(self):
+            def test_1(self) -> None:
                 self.first = "test1Run"
 
-            def test_2(self):
+            def test_2(self) -> None:
                 self.assertFalse(hasattr(self, "first"))
 
         suite = self.loader.loadClass(DistinctInstances)
@@ -430,7 +434,7 @@ def test_2(self):
         suite.run(result)
         self.assertTrue(result.wasSuccessful())
 
-    def test_loadModuleWith_test_suite(self):
+    def test_loadModuleWith_test_suite(self) -> None:
         """
         Check that C{test_suite} is used when present and other L{TestCase}s are
         not included.
@@ -441,7 +445,7 @@ def test_loadModuleWith_test_suite(self):
         self.assertEqual(0, suite.countTestCases())
         self.assertEqual("MyCustomSuite", getattr(suite, "name", None))
 
-    def test_loadModuleWith_testSuite(self):
+    def test_loadModuleWith_testSuite(self) -> None:
         """
         Check that C{testSuite} is used when present and other L{TestCase}s are
         not included.
@@ -452,7 +456,7 @@ def test_loadModuleWith_testSuite(self):
         self.assertEqual(0, suite.countTestCases())
         self.assertEqual("MyCustomSuite", getattr(suite, "name", None))
 
-    def test_loadModuleWithBothCustom(self):
+    def test_loadModuleWithBothCustom(self) -> None:
         """
         Check that if C{testSuite} and C{test_suite} are both present in a
         module then C{testSuite} gets priority.
@@ -462,13 +466,13 @@ def test_loadModuleWithBothCustom(self):
         suite = self.loader.loadModule(mockcustomsuite3)
         self.assertEqual("testSuite", getattr(suite, "name", None))
 
-    def test_customLoadRaisesAttributeError(self):
+    def test_customLoadRaisesAttributeError(self) -> None:
         """
         Make sure that any C{AttributeError}s raised by C{testSuite} are not
         swallowed by L{TestLoader}.
         """
 
-        def testSuite():
+        def testSuite() -> None:
             raise AttributeError("should be reraised")
 
         from twisted.trial.test import mockcustomsuite2
@@ -480,14 +484,18 @@ def testSuite():
             mockcustomsuite2.testSuite = original
 
     # XXX - duplicated and modified from test_script
-    def assertSuitesEqual(self, test1, test2):
+    def assertSuitesEqual(
+        self,
+        test1: pyunit.TestCase | pyunit.TestSuite,
+        test2: pyunit.TestCase | pyunit.TestSuite,
+    ) -> None:
         names1 = testNames(test1)
         names2 = testNames(test2)
         names1.sort()
         names2.sort()
         self.assertEqual(names1, names2)
 
-    def test_loadByNamesDuplicate(self):
+    def test_loadByNamesDuplicate(self) -> None:
         """
         Check that loadByNames ignores duplicate names
         """
@@ -496,7 +504,7 @@ def test_loadByNamesDuplicate(self):
         suite2 = self.loader.loadByName(module, True)
         self.assertSuitesEqual(suite1, suite2)
 
-    def test_loadByNamesPreservesOrder(self):
+    def test_loadByNamesPreservesOrder(self) -> None:
         """
         L{TestLoader.loadByNames} preserves the order of tests provided to it.
         """
@@ -512,7 +520,7 @@ def test_loadByNamesPreservesOrder(self):
         suite2 = runner.TestSuite(map(self.loader.loadByName, modules))
         self.assertEqual(testNames(suite1), testNames(suite2))
 
-    def test_loadDifferentNames(self):
+    def test_loadDifferentNames(self) -> None:
         """
         Check that loadByNames loads all the names that it is given
         """
@@ -521,7 +529,7 @@ def test_loadDifferentNames(self):
         suite2 = runner.TestSuite(map(self.loader.loadByName, modules))
         self.assertSuitesEqual(suite1, suite2)
 
-    def test_loadInheritedMethods(self):
+    def test_loadInheritedMethods(self) -> None:
         """
         Check that test methods names which are inherited from are all
         loaded rather than just one.
@@ -536,7 +544,7 @@ def test_loadInheritedMethods(self):
 
 
 class ZipLoadingTests(LoaderTests):
-    def setUp(self):
+    def setUp(self) -> None:  # type: ignore[override]
         from twisted.python.test.test_zippath import zipit
 
         LoaderTests.setUp(self)
@@ -546,7 +554,7 @@ def setUp(self):
 
 
 class PackageOrderingTests(packages.SysPathManglingTest):
-    def setUp(self):
+    def setUp(self) -> None:  # type: ignore[override]
         self.loader = runner.TestLoader()
         self.topDir = self.mktemp()
         parent = os.path.join(self.topDir, "uberpackage")
@@ -555,7 +563,9 @@ def setUp(self):
         packages.SysPathManglingTest.setUp(self, parent)
         self.mangleSysPath(self.oldPath + [self.topDir])
 
-    def _trialSortAlgorithm(self, sorter):
+    def _trialSortAlgorithm(
+        self, sorter: Callable[[PythonModule | PythonAttribute], SupportsRichComparison]
+    ) -> Generator[PythonModule | PythonAttribute, None, None]:
         """
         Right now, halfway by accident, trial sorts like this:
 
@@ -598,7 +608,9 @@ def _trialSortAlgorithm(self, sorter):
                     sortedMethods = sorted(testMethods, key=sorter)  # THREE
                     yield from sortedMethods
 
-    def loadSortedPackages(self, sorter=runner.name):
+    def loadSortedPackages(
+        self, sorter: Callable[[runner._Loadable], SupportsRichComparison] = runner.name
+    ) -> None:
         """
         Verify that packages are loaded in the correct order.
         """
@@ -621,11 +633,11 @@ def loadSortedPackages(self, sorter=runner.name):
             )
         self.assertEqual(len(manifest), len(resultingTests))
 
-    def test_sortPackagesDefaultOrder(self):
+    def test_sortPackagesDefaultOrder(self) -> None:
         self.loadSortedPackages()
 
-    def test_sortPackagesSillyOrder(self):
-        def sillySorter(s):
+    def test_sortPackagesSillyOrder(self) -> None:
+        def sillySorter(s: runner._Loadable) -> str:
             # This has to work on fully-qualified class names and class
             # objects, which is silly, but it's the "spec", such as it is.
             #             if isinstance(s, type):
diff --git a/src/twisted/trial/test/test_pyunitcompat.py b/src/twisted/trial/test/test_pyunitcompat.py
--- a/src/twisted/trial/test/test_pyunitcompat.py
+++ b/src/twisted/trial/test/test_pyunitcompat.py
@@ -1,6 +1,6 @@
 # Copyright (c) Twisted Matrix Laboratories.
 # See LICENSE for details.
-
+from __future__ import annotations
 
 import sys
 import traceback
@@ -16,11 +16,11 @@
 
 
 class PyUnitTestTests(SynchronousTestCase):
-    def setUp(self):
+    def setUp(self) -> None:
         self.original = pyunitcases.PyUnitTest("test_pass")
         self.test = ITestCase(self.original)
 
-    def test_callable(self):
+    def test_callable(self) -> None:
         """
         Tests must be callable in order to be used with Python's unittest.py.
         """
@@ -45,21 +45,21 @@ class ErrorTest(SynchronousTestCase):
 
         ran = False
 
-        def test_foo(self):
+        def test_foo(self) -> None:
             """
             Set C{self.ran} to True and raise a C{ZeroDivisionError}
             """
             self.ran = True
             1 / 0
 
-    def test_dontUseAdapterWhenReporterProvidesIReporter(self):
+    def test_dontUseAdapterWhenReporterProvidesIReporter(self) -> None:
         """
         The L{PyUnitResultAdapter} is only used when the result passed to
         C{run} does *not* provide L{IReporter}.
         """
 
         @implementer(IReporter)
-        class StubReporter:
+        class StubReporter:  # type: ignore[misc]
             """
             A reporter which records data about calls made to it.
 
@@ -67,21 +67,21 @@ class StubReporter:
             @ivar failures: Failures passed to L{addFailure}.
             """
 
-            def __init__(self):
-                self.errors = []
-                self.failures = []
+            def __init__(self) -> None:
+                self.errors: list[Failure] = []
+                self.failures: list[None] = []
 
-            def startTest(self, test):
+            def startTest(self, test: object) -> None:
                 """
                 Do nothing.
                 """
 
-            def stopTest(self, test):
+            def stopTest(self, test: object) -> None:
                 """
                 Do nothing.
                 """
 
-            def addError(self, test, error):
+            def addError(self, test: object, error: Failure) -> None:
                 """
                 Record the error.
                 """
@@ -92,11 +92,11 @@ def addError(self, test, error):
         test.run(result)
         self.assertIsInstance(result.errors[0], Failure)
 
-    def test_success(self):
+    def test_success(self) -> None:
         class SuccessTest(SynchronousTestCase):
             ran = False
 
-            def test_foo(s):
+            def test_foo(s) -> None:
                 s.ran = True
 
         test = SuccessTest("test_foo")
@@ -107,11 +107,11 @@ def test_foo(s):
         self.assertEqual(1, result.testsRun)
         self.assertTrue(result.wasSuccessful())
 
-    def test_failure(self):
+    def test_failure(self) -> None:
         class FailureTest(SynchronousTestCase):
             ran = False
 
-            def test_foo(s):
+            def test_foo(s) -> None:
                 s.ran = True
                 s.fail("boom!")
 
@@ -124,7 +124,7 @@ def test_foo(s):
         self.assertEqual(1, len(result.failures))
         self.assertFalse(result.wasSuccessful())
 
-    def test_error(self):
+    def test_error(self) -> None:
         test = self.ErrorTest("test_foo")
         result = pyunit.TestResult()
         test.run(result)
@@ -134,14 +134,14 @@ def test_error(self):
         self.assertEqual(1, len(result.errors))
         self.assertFalse(result.wasSuccessful())
 
-    def test_setUpError(self):
+    def test_setUpError(self) -> None:
         class ErrorTest(SynchronousTestCase):
             ran = False
 
-            def setUp(self):
+            def setUp(self) -> None:
                 1 / 0
 
-            def test_foo(s):
+            def test_foo(s) -> None:
                 s.ran = True
 
         test = ErrorTest("test_foo")
@@ -153,7 +153,7 @@ def test_foo(s):
         self.assertEqual(1, len(result.errors))
         self.assertFalse(result.wasSuccessful())
 
-    def test_tracebackFromFailure(self):
+    def test_tracebackFromFailure(self) -> None:
         """
         Errors added through the L{PyUnitResultAdapter} have the same traceback
         information as if there were no adapter at all.
@@ -170,7 +170,7 @@ def test_tracebackFromFailure(self):
             pyresult.errors[0][1], "".join(traceback.format_exception(*exc_info))
         )
 
-    def test_traceback(self):
+    def test_traceback(self) -> None:
         """
         As test_tracebackFromFailure, but covering more code.
         """
@@ -178,7 +178,7 @@ def test_traceback(self):
         class ErrorTest(SynchronousTestCase):
             exc_info = None
 
-            def test_foo(self):
+            def test_foo(self) -> None:
                 try:
                     1 / 0
                 except ZeroDivisionError:
@@ -195,7 +195,7 @@ def test_foo(self):
         #
         # So, we just test that the result's stack ends with the
         # exception's stack.
-
+        assert test.exc_info is not None
         expected_stack = "".join(traceback.format_tb(test.exc_info[2]))
         observed_stack = "\n".join(result.errors[0][1].splitlines()[:-1])
 
@@ -203,7 +203,7 @@ def test_foo(self):
             expected_stack.strip(), observed_stack[-len(expected_stack) :].strip()
         )
 
-    def test_tracebackFromCleanFailure(self):
+    def test_tracebackFromCleanFailure(self) -> None:
         """
         Errors added through the L{PyUnitResultAdapter} have the same
         traceback information as if there were no adapter at all, even
@@ -224,7 +224,7 @@ def test_tracebackFromCleanFailure(self):
             tback.endswith("ZeroDivisionError: division by zero\n"),
         )
 
-    def test_trialSkip(self):
+    def test_trialSkip(self) -> None:
         """
         Skips using trial's skipping functionality are reported as skips in
         the L{pyunit.TestResult}.
@@ -232,7 +232,7 @@ def test_trialSkip(self):
 
         class SkipTest(SynchronousTestCase):
             @skipIf(True, "Let's skip!")
-            def test_skip(self):
+            def test_skip(self) -> None:
                 1 / 0
 
         test = SkipTest("test_skip")
@@ -240,7 +240,7 @@ def test_skip(self):
         test.run(result)
         self.assertEqual(result.skipped, [(test, "Let's skip!")])
 
-    def test_pyunitSkip(self):
+    def test_pyunitSkip(self) -> None:
         """
         Skips using pyunit's skipping functionality are reported as skips in
         the L{pyunit.TestResult}.
@@ -248,7 +248,7 @@ def test_pyunitSkip(self):
 
         class SkipTest(SynchronousTestCase):
             @pyunit.skip("skippy")
-            def test_skip(self):
+            def test_skip(self) -> None:
                 1 / 0
 
         test = SkipTest("test_skip")
diff --git a/src/twisted/trial/test/test_script.py b/src/twisted/trial/test/test_script.py
--- a/src/twisted/trial/test/test_script.py
+++ b/src/twisted/trial/test/test_script.py
@@ -1,6 +1,6 @@
 # Copyright (c) Twisted Matrix Laboratories.
 # See LICENSE for details.
-
+from __future__ import annotations
 
 import gc
 import re
@@ -27,6 +27,7 @@
     TestLoader,
     TestSuite,
     TrialRunner,
+    _Runner,
 )
 from twisted.trial.test.test_loader import testNames
 from .matchers import fileContents
@@ -34,7 +35,7 @@
 pyunit = __import__("unittest")
 
 
-def sibpath(filename):
+def sibpath(filename: str) -> str:
     """
     For finding files in twisted/trial/test
     """
@@ -140,26 +141,26 @@ class ForceGarbageCollectionTests(unittest.SynchronousTestCase):
     Tests for the --force-gc option.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         self.config = trial.Options()
-        self.log = []
+        self.log: list[str] = []
         self.patch(gc, "collect", self.collect)
         test = pyunit.FunctionTestCase(self.simpleTest)
         self.test = TestSuite([test, test])
 
-    def simpleTest(self):
+    def simpleTest(self) -> None:
         """
         A simple test method that records that it was run.
         """
         self.log.append("test")
 
-    def collect(self):
+    def collect(self) -> None:
         """
         A replacement for gc.collect that logs calls to itself.
         """
         self.log.append("collect")
 
-    def makeRunner(self):
+    def makeRunner(self) -> _Runner:
         """
         Return a L{TrialRunner} object that is safe to use in tests.
         """
@@ -167,7 +168,7 @@ def makeRunner(self):
         runner.stream = StringIO()
         return runner
 
-    def test_forceGc(self):
+    def test_forceGc(self) -> None:
         """
         Passing the --force-gc option to the trial script forces the garbage
         collector to run before and after each test.
@@ -180,7 +181,7 @@ def test_forceGc(self):
             self.log, ["collect", "test", "collect", "collect", "test", "collect"]
         )
 
-    def test_unforceGc(self):
+    def test_unforceGc(self) -> None:
         """
         By default, no garbage collection is forced.
         """
@@ -195,20 +196,20 @@ class SuiteUsedTests(unittest.SynchronousTestCase):
     Check the category of tests suite used by the loader.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Create a trial configuration object.
         """
         self.config = trial.Options()
 
-    def test_defaultSuite(self):
+    def test_defaultSuite(self) -> None:
         """
         By default, the loader should use L{DestructiveTestSuite}
         """
         loader = trial._getLoader(self.config)
         self.assertEqual(loader.suiteFactory, DestructiveTestSuite)
 
-    def test_untilFailureSuite(self):
+    def test_untilFailureSuite(self) -> None:
         """
         The C{until-failure} configuration uses the L{TestSuite} to keep
         instances alive across runs.
@@ -219,20 +220,20 @@ def test_untilFailureSuite(self):
 
 
 class TestModuleTests(unittest.SynchronousTestCase):
-    def setUp(self):
+    def setUp(self) -> None:
         self.config = trial.Options()
 
-    def tearDown(self):
-        self.config = None
+    def tearDown(self) -> None:
+        self.config = None  # type: ignore[assignment]
 
-    def test_testNames(self):
+    def test_testNames(self) -> None:
         """
         Check that the testNames helper method accurately collects the
         names of tests in suite.
         """
         self.assertEqual(testNames(self), [self.id()])
 
-    def assertSuitesEqual(self, test1, names):
+    def assertSuitesEqual(self, test1: TestSuite, names: list[str]) -> None:
         loader = TestLoader()
         names1 = testNames(test1)
         names2 = testNames(TestSuite(map(loader.loadByName, names)))
@@ -240,10 +241,10 @@ def assertSuitesEqual(self, test1, names):
         names2.sort()
         self.assertEqual(names1, names2)
 
-    def test_baseState(self):
+    def test_baseState(self) -> None:
         self.assertEqual(0, len(self.config["tests"]))
 
-    def test_testmoduleOnModule(self):
+    def test_testmoduleOnModule(self) -> None:
         """
         Check that --testmodule loads a suite which contains the tests
         referred to in test-case-name inside its parameter.
@@ -253,7 +254,7 @@ def test_testmoduleOnModule(self):
             trial._getSuite(self.config), ["twisted.trial.test.test_log"]
         )
 
-    def test_testmoduleTwice(self):
+    def test_testmoduleTwice(self) -> None:
         """
         When the same module is specified with two --testmodule flags, it
         should only appear once in the suite.
@@ -264,7 +265,7 @@ def test_testmoduleTwice(self):
             trial._getSuite(self.config), ["twisted.trial.test.test_log"]
         )
 
-    def test_testmoduleOnSourceAndTarget(self):
+    def test_testmoduleOnSourceAndTarget(self) -> None:
         """
         If --testmodule is specified twice, once for module A and once for
         a module which refers to module A, then make sure module A is only
@@ -276,7 +277,7 @@ def test_testmoduleOnSourceAndTarget(self):
             trial._getSuite(self.config), ["twisted.trial.test.test_log"]
         )
 
-    def test_testmoduleOnSelfModule(self):
+    def test_testmoduleOnSelfModule(self) -> None:
         """
         When given a module that refers to *itself* in the test-case-name
         variable, check that --testmodule only adds the tests once.
@@ -286,7 +287,7 @@ def test_testmoduleOnSelfModule(self):
             trial._getSuite(self.config), ["twisted.trial.test.moduleself"]
         )
 
-    def test_testmoduleOnScript(self):
+    def test_testmoduleOnScript(self) -> None:
         """
         Check that --testmodule loads tests referred to in test-case-name
         buffer variables.
@@ -297,7 +298,7 @@ def test_testmoduleOnScript(self):
             ["twisted.trial.test.test_log", "twisted.trial.test.test_runner"],
         )
 
-    def test_testmoduleOnNonexistentFile(self):
+    def test_testmoduleOnNonexistentFile(self) -> None:
         """
         Check that --testmodule displays a meaningful error message when
         passed a non-existent filename.
@@ -312,7 +313,7 @@ def test_testmoduleOnNonexistentFile(self):
         finally:
             sys.stderr = stderr
 
-    def test_testmoduleOnEmptyVars(self):
+    def test_testmoduleOnEmptyVars(self) -> None:
         """
         Check that --testmodule adds no tests to the suite for modules
         which lack test-case-name buffer variables.
@@ -320,7 +321,7 @@ def test_testmoduleOnEmptyVars(self):
         self.config.opt_testmodule(sibpath("novars.py"))
         self.assertEqual(0, len(self.config["tests"]))
 
-    def test_testmoduleOnModuleName(self):
+    def test_testmoduleOnModuleName(self) -> None:
         """
         Check that --testmodule does *not* support module names as arguments
         and that it displays a meaningful error message.
@@ -335,17 +336,17 @@ def test_testmoduleOnModuleName(self):
         finally:
             sys.stderr = stderr
 
-    def test_parseLocalVariable(self):
+    def test_parseLocalVariable(self) -> None:
         declaration = "-*- test-case-name: twisted.trial.test.test_tests -*-"
         localVars = trial._parseLocalVariables(declaration)
         self.assertEqual({"test-case-name": "twisted.trial.test.test_tests"}, localVars)
 
-    def test_trailingSemicolon(self):
+    def test_trailingSemicolon(self) -> None:
         declaration = "-*- test-case-name: twisted.trial.test.test_tests; -*-"
         localVars = trial._parseLocalVariables(declaration)
         self.assertEqual({"test-case-name": "twisted.trial.test.test_tests"}, localVars)
 
-    def test_parseLocalVariables(self):
+    def test_parseLocalVariables(self) -> None:
         declaration = (
             "-*- test-case-name: twisted.trial.test.test_tests; " "foo: bar -*-"
         )
@@ -354,15 +355,15 @@ def test_parseLocalVariables(self):
             {"test-case-name": "twisted.trial.test.test_tests", "foo": "bar"}, localVars
         )
 
-    def test_surroundingGuff(self):
+    def test_surroundingGuff(self) -> None:
         declaration = "## -*- test-case-name: " "twisted.trial.test.test_tests -*- #"
         localVars = trial._parseLocalVariables(declaration)
         self.assertEqual({"test-case-name": "twisted.trial.test.test_tests"}, localVars)
 
-    def test_invalidLine(self):
+    def test_invalidLine(self) -> None:
         self.assertRaises(ValueError, trial._parseLocalVariables, "foo")
 
-    def test_invalidDeclaration(self):
+    def test_invalidDeclaration(self) -> None:
         self.assertRaises(ValueError, trial._parseLocalVariables, "-*- foo -*-")
         self.assertRaises(
             ValueError, trial._parseLocalVariables, "-*- foo: bar; qux -*-"
@@ -371,15 +372,15 @@ def test_invalidDeclaration(self):
             ValueError, trial._parseLocalVariables, "-*- foo: bar: baz; qux: qax -*-"
         )
 
-    def test_variablesFromFile(self):
+    def test_variablesFromFile(self) -> None:
         localVars = trial.loadLocalVariables(sibpath("moduletest.py"))
         self.assertEqual({"test-case-name": "twisted.trial.test.test_log"}, localVars)
 
-    def test_noVariablesInFile(self):
+    def test_noVariablesInFile(self) -> None:
         localVars = trial.loadLocalVariables(sibpath("novars.py"))
         self.assertEqual({}, localVars)
 
-    def test_variablesFromScript(self):
+    def test_variablesFromScript(self) -> None:
         localVars = trial.loadLocalVariables(sibpath("scripttest.py"))
         self.assertEqual(
             {
@@ -390,22 +391,22 @@ def test_variablesFromScript(self):
             localVars,
         )
 
-    def test_getTestModules(self):
+    def test_getTestModules(self) -> None:
         modules = trial.getTestModules(sibpath("moduletest.py"))
         self.assertEqual(modules, ["twisted.trial.test.test_log"])
 
-    def test_getTestModules_noVars(self):
+    def test_getTestModules_noVars(self) -> None:
         modules = trial.getTestModules(sibpath("novars.py"))
         self.assertEqual(len(modules), 0)
 
-    def test_getTestModules_multiple(self):
+    def test_getTestModules_multiple(self) -> None:
         modules = trial.getTestModules(sibpath("scripttest.py"))
         self.assertEqual(
             set(modules),
             {"twisted.trial.test.test_log", "twisted.trial.test.test_runner"},
         )
 
-    def test_looksLikeTestModule(self):
+    def test_looksLikeTestModule(self) -> None:
         for filename in ["test_script.py", "twisted/trial/test/test_script.py"]:
             self.assertTrue(
                 trial.isTestFile(filename),
@@ -427,7 +428,7 @@ class WithoutModuleTests(unittest.SynchronousTestCase):
     Test the C{without-module} flag.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Create a L{trial.Options} object to be used in the tests, and save
         C{sys.modules}.
@@ -435,7 +436,7 @@ def setUp(self):
         self.config = trial.Options()
         self.savedModules = dict(sys.modules)
 
-    def tearDown(self):
+    def tearDown(self) -> None:
         """
         Restore C{sys.modules}.
         """
@@ -445,7 +446,7 @@ def tearDown(self):
             else:
                 sys.modules.pop(module, None)
 
-    def _checkSMTP(self):
+    def _checkSMTP(self) -> object:
         """
         Try to import the C{smtplib} module, and return it.
         """
@@ -453,7 +454,7 @@ def _checkSMTP(self):
 
         return smtplib
 
-    def _checkIMAP(self):
+    def _checkIMAP(self) -> object:
         """
         Try to import the C{imaplib} module, and return it.
         """
@@ -461,7 +462,7 @@ def _checkIMAP(self):
 
         return imaplib
 
-    def test_disableOneModule(self):
+    def test_disableOneModule(self) -> None:
         """
         Check that after disabling a module, it can't be imported anymore.
         """
@@ -472,7 +473,7 @@ def test_disableOneModule(self):
         # Then the function should succeed
         self.assertIsInstance(self._checkSMTP(), types.ModuleType)
 
-    def test_disableMultipleModules(self):
+    def test_disableMultipleModules(self) -> None:
         """
         Check that several modules can be disabled at once.
         """
@@ -486,7 +487,7 @@ def test_disableMultipleModules(self):
         self.assertIsInstance(self._checkSMTP(), types.ModuleType)
         self.assertIsInstance(self._checkIMAP(), types.ModuleType)
 
-    def test_disableAlreadyImportedModule(self):
+    def test_disableAlreadyImportedModule(self) -> None:
         """
         Disabling an already imported module should produce a warning.
         """
@@ -509,23 +510,28 @@ class CoverageTests(unittest.SynchronousTestCase):
     if getattr(sys, "gettrace", None) is None:
         skip = "Cannot test trace hook installation without inspection API."
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Arrange for the current trace hook to be restored when the
         test is complete.
         """
         self.addCleanup(sys.settrace, sys.gettrace())
 
-    def test_tracerInstalled(self):
+    def test_tracerInstalled(self) -> None:
         """
         L{trial.Options} handles C{"--coverage"} by installing a trace
         hook to record coverage information.
         """
         options = trial.Options()
         options.parseOptions(["--coverage"])
-        self.assertEqual(sys.gettrace(), options.tracer.globaltrace)
+        assert options.tracer is not None
+        self.assertEqual(
+            sys.gettrace(),
+            # the globaltrace attribute of trace.Trace is undocumented
+            options.tracer.globaltrace,  # type: ignore[attr-defined]
+        )
 
-    def test_coverdirDefault(self):
+    def test_coverdirDefault(self) -> None:
         """
         L{trial.Options.coverdir} returns a L{FilePath} based on the default
         for the I{temp-directory} option if that option is not specified.
@@ -536,7 +542,7 @@ def test_coverdirDefault(self):
             FilePath(".").descendant([options["temp-directory"], "coverage"]),
         )
 
-    def test_coverdirOverridden(self):
+    def test_coverdirOverridden(self) -> None:
         """
         If a value is specified for the I{temp-directory} option,
         L{trial.Options.coverdir} returns a child of that path.
@@ -552,13 +558,13 @@ class OptionsTests(unittest.TestCase):
     Tests for L{trial.Options}.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         """
         Build an L{Options} object to be used in the tests.
         """
         self.options = trial.Options()
 
-    def test_getWorkerArguments(self):
+    def test_getWorkerArguments(self) -> None:
         """
         C{_getWorkerArguments} discards options like C{random} as they only
         matter in the manager, and forwards options like C{recursionlimit} or
@@ -576,7 +582,7 @@ def test_getWorkerArguments(self):
         args.remove("--disablegc")
         self.assertEqual(["--recursionlimit", "2000"], args)
 
-    def test_jobsConflictWithDebug(self):
+    def test_jobsConflictWithDebug(self) -> None:
         """
         C{parseOptions} raises a C{UsageError} when C{--debug} is passed along
         C{--jobs} as it's not supported yet.
@@ -588,7 +594,7 @@ def test_jobsConflictWithDebug(self):
         )
         self.assertEqual("You can't specify --debug when using --jobs", str(error))
 
-    def test_jobsConflictWithProfile(self):
+    def test_jobsConflictWithProfile(self) -> None:
         """
         C{parseOptions} raises a C{UsageError} when C{--profile} is passed
         along C{--jobs} as it's not supported yet.
@@ -600,7 +606,7 @@ def test_jobsConflictWithProfile(self):
         )
         self.assertEqual("You can't specify --profile when using --jobs", str(error))
 
-    def test_jobsConflictWithDebugStackTraces(self):
+    def test_jobsConflictWithDebugStackTraces(self) -> None:
         """
         C{parseOptions} raises a C{UsageError} when C{--debug-stacktraces} is
         passed along C{--jobs} as it's not supported yet.
@@ -616,7 +622,7 @@ def test_jobsConflictWithDebugStackTraces(self):
             "You can't specify --debug-stacktraces when using --jobs", str(error)
         )
 
-    def test_orderConflictWithRandom(self):
+    def test_orderConflictWithRandom(self) -> None:
         """
         C{parseOptions} raises a C{UsageError} when C{--order} is passed along
         with C{--random}.
@@ -634,10 +640,10 @@ class MakeRunnerTests(unittest.TestCase):
     Tests for the L{_makeRunner} helper.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         self.options = trial.Options()
 
-    def test_jobs(self):
+    def test_jobs(self) -> None:
         """
         L{_makeRunner} returns a L{DistTrialRunner} instance when the C{--jobs}
         option is passed.  The L{DistTrialRunner} knows how many workers to
@@ -645,24 +651,26 @@ def test_jobs(self):
         """
         self.options.parseOptions(["--jobs", "4", "--force-gc"])
         runner = trial._makeRunner(self.options)
+        assert isinstance(runner, DistTrialRunner)
         self.assertIsInstance(runner, DistTrialRunner)
         self.assertEqual(4, runner._maxWorkers)
         self.assertEqual(["--force-gc"], runner._workerArguments)
 
-    def test_dryRunWithJobs(self):
+    def test_dryRunWithJobs(self) -> None:
         """
         L{_makeRunner} returns a L{TrialRunner} instance in C{DRY_RUN} mode
         when the C{--dry-run} option is passed, even if C{--jobs} is set.
         """
         self.options.parseOptions(["--jobs", "4", "--dry-run"])
         runner = trial._makeRunner(self.options)
+        assert isinstance(runner, TrialRunner)
         self.assertIsInstance(runner, TrialRunner)
         self.assertEqual(TrialRunner.DRY_RUN, runner.mode)
 
-    def test_DebuggerNotFound(self):
+    def test_DebuggerNotFound(self) -> None:
         namedAny = trial.reflect.namedAny
 
-        def namedAnyExceptdoNotFind(fqn):
+        def namedAnyExceptdoNotFind(fqn: str) -> object:
             if fqn == "doNotFind":
                 raise trial.reflect.ModuleNotFound(fqn)
             return namedAny(fqn)
@@ -674,13 +682,14 @@ def namedAnyExceptdoNotFind(fqn):
 
         self.assertRaises(trial._DebuggerNotFound, trial._makeRunner, options)
 
-    def test_exitfirst(self):
+    def test_exitfirst(self) -> None:
         """
         Passing C{--exitfirst} wraps the reporter with a
         L{reporter._ExitWrapper} that stops on any non-success.
         """
         self.options.parseOptions(["--exitfirst"])
         runner = trial._makeRunner(self.options)
+        assert isinstance(runner, TrialRunner)
         self.assertTrue(runner._exitFirst)
 
 
@@ -689,18 +698,18 @@ class RunTests(unittest.TestCase):
     Tests for the L{run} function.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         # don't re-parse cmdline options, because if --reactor was passed to
         # the test run trial will try to restart the (already running) reactor
         self.patch(trial.Options, "parseOptions", lambda self: None)
 
-    def test_debuggerNotFound(self):
+    def test_debuggerNotFound(self) -> None:
         """
         When a debugger is not found, an error message is printed to the user.
 
         """
 
-        def _makeRunner(*args, **kwargs):
+        def _makeRunner(*args: object, **kwargs: object) -> None:
             raise trial._DebuggerNotFound("foo")
 
         self.patch(trial, "_makeRunner", _makeRunner)
@@ -718,11 +727,11 @@ class TestArgumentOrderTests(unittest.TestCase):
     Tests for the order-preserving behavior on provided command-line tests.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         self.config = trial.Options()
         self.loader = TestLoader()
 
-    def test_preserveArgumentOrder(self):
+    def test_preserveArgumentOrder(self) -> None:
         """
         Multiple tests passed on the command line are not reordered.
         """
@@ -747,10 +756,10 @@ class OrderTests(unittest.TestCase):
     Tests for the --order option.
     """
 
-    def setUp(self):
+    def setUp(self) -> None:
         self.config = trial.Options()
 
-    def test_alphabetical(self):
+    def test_alphabetical(self) -> None:
         """
         --order=alphabetical causes trial to run tests alphabetically within
         each test case.
@@ -772,7 +781,7 @@ def test_alphabetical(self):
             ],
         )
 
-    def test_alphabeticalModule(self):
+    def test_alphabeticalModule(self) -> None:
         """
         --order=alphabetical causes trial to run test classes within a given
         module alphabetically.
@@ -795,7 +804,7 @@ def test_alphabeticalModule(self):
             ],
         )
 
-    def test_alphabeticalPackage(self):
+    def test_alphabeticalPackage(self) -> None:
         """
         --order=alphabetical causes trial to run test modules within a given
         package alphabetically, with tests within each module alphabetized.
@@ -808,7 +817,7 @@ def test_alphabeticalPackage(self):
         self.assertTrue(names, msg="Failed to load any tests!")
         self.assertEqual(names, sorted(names))
 
-    def test_toptobottom(self):
+    def test_toptobottom(self) -> None:
         """
         --order=toptobottom causes trial to run test methods within a given
         test case from top to bottom as they are defined in the body of the
@@ -831,7 +840,7 @@ def test_toptobottom(self):
             ],
         )
 
-    def test_toptobottomModule(self):
+    def test_toptobottomModule(self) -> None:
         """
         --order=toptobottom causes trial to run test classes within a given
         module from top to bottom as they are defined in the module's source.
@@ -854,7 +863,7 @@ def test_toptobottomModule(self):
             ],
         )
 
-    def test_toptobottomPackage(self):
+    def test_toptobottomPackage(self) -> None:
         """
         --order=toptobottom causes trial to run test modules within a given
         package alphabetically, with tests within each module run top to
@@ -872,7 +881,7 @@ def test_toptobottomPackage(self):
             sorted(names, key=lambda name: name.split(".")[:4]),
         )
 
-    def test_toptobottomMissingSource(self):
+    def test_toptobottomMissingSource(self) -> None:
         """
         --order=toptobottom detects the source line of methods from modules
         whose source file is missing.
@@ -886,10 +895,10 @@ def test_toptobottomMissingSource(self):
                 """
         from twisted.trial.unittest import TestCase
         class TestMissing(TestCase):
-            def test_second(self): pass
-            def test_third(self): pass
-            def test_fourth(self): pass
-            def test_first(self): pass
+            def test_second(self) -> None: pass
+            def test_third(self) -> None: pass
+            def test_fourth(self) -> None: pass
+            def test_first(self) -> None: pass
         """
             ).encode("utf8")
         )
@@ -918,7 +927,7 @@ def test_first(self): pass
             ],
         )
 
-    def test_unknownOrder(self):
+    def test_unknownOrder(self) -> None:
         """
         An unknown order passed to --order raises a L{UsageError}.
         """
@@ -933,18 +942,18 @@ class HelpOrderTests(unittest.TestCase):
     Tests for the --help-orders flag.
     """
 
-    def test_help_ordersPrintsSynopsisAndQuits(self):
+    def test_help_ordersPrintsSynopsisAndQuits(self) -> None:
         """
         --help-orders prints each of the available orders and then exits.
         """
-        self.patch(sys, "stdout", StringIO())
+        self.patch(sys, "stdout", stdout := StringIO())
 
         exc = self.assertRaises(
             SystemExit, trial.Options().parseOptions, ["--help-orders"]
         )
         self.assertEqual(exc.code, 0)
 
-        output = sys.stdout.getvalue()
+        output = stdout.getvalue()
 
         msg = "%r with its description not properly described in %r"
         for orderName, (orderDesc, _) in trial._runOrders.items():
diff --git a/src/twisted/trial/test/test_util.py b/src/twisted/trial/test/test_util.py
--- a/src/twisted/trial/test/test_util.py
+++ b/src/twisted/trial/test/test_util.py
@@ -5,12 +5,13 @@
 """
 Tests for L{twisted.trial.util}
 """
-
+from __future__ import annotations
 
 import locale
 import os
 import sys
 from io import StringIO
+from typing import Generator
 
 from zope.interface import implementer
 
@@ -37,7 +38,7 @@ class MktempTests(SynchronousTestCase):
     or directory names.
     """
 
-    def test_name(self):
+    def test_name(self) -> None:
         """
         The path name returned by C{mktemp} is directly beneath a directory
         which identifies the test method which created the name.
@@ -48,14 +49,14 @@ def test_name(self):
             dirs, ["twisted.trial.test.test_util", "MktempTests", "test_name"]
         )
 
-    def test_unique(self):
+    def test_unique(self) -> None:
         """
         Repeated calls to C{mktemp} return different values.
         """
         name = self.mktemp()
         self.assertNotEqual(name, self.mktemp())
 
-    def test_created(self):
+    def test_created(self) -> None:
         """
         The directory part of the path name returned by C{mktemp} exists.
         """
@@ -64,7 +65,7 @@ def test_created(self):
         self.assertTrue(os.path.exists(dirname))
         self.assertFalse(os.path.exists(name))
 
-    def test_location(self):
+    def test_location(self) -> None:
         """
         The path returned by C{mktemp} is beneath the current working directory.
         """
@@ -77,7 +78,7 @@ class DirtyReactorAggregateErrorTests(SynchronousTestCase):
     Tests for the L{DirtyReactorAggregateError}.
     """
 
-    def test_formatDelayedCall(self):
+    def test_formatDelayedCall(self) -> None:
         """
         Delayed calls are formatted nicely.
         """
@@ -91,7 +92,7 @@ def test_formatDelayedCall(self):
 bar""",
         )
 
-    def test_formatSelectables(self):
+    def test_formatSelectables(self) -> None:
         """
         Selectables are formatted nicely.
         """
@@ -105,7 +106,7 @@ def test_formatSelectables(self):
 selectable 2""",
         )
 
-    def test_formatDelayedCallsAndSelectables(self):
+    def test_formatDelayedCallsAndSelectables(self) -> None:
         """
         Both delayed calls and selectables can appear in the same error.
         """
@@ -134,31 +135,33 @@ class StubReactor:
     @ivar delayedCalls: The value to return from L{getDelayedCalls}.
     """
 
-    def __init__(self, delayedCalls, selectables=None):
+    def __init__(
+        self, delayedCalls: list[DelayedCall], selectables: list[object] | None = None
+    ) -> None:
         """
         @param delayedCalls: See L{StubReactor.delayedCalls}.
         @param selectables: See L{StubReactor.selectables}.
         """
         self.delayedCalls = delayedCalls
-        self.iterations = []
+        self.iterations: list[float | None] = []
         self.removeAllCalled = 0
         if not selectables:
             selectables = []
         self.selectables = selectables
 
-    def iterate(self, timeout=None):
+    def iterate(self, timeout: float | None = None) -> None:
         """
         Increment C{self.iterations}.
         """
         self.iterations.append(timeout)
 
-    def getDelayedCalls(self):
+    def getDelayedCalls(self) -> list[DelayedCall]:
         """
         Return C{self.delayedCalls}.
         """
         return self.delayedCalls
 
-    def removeAll(self):
+    def removeAll(self) -> list[object]:
         """
         Increment C{self.removeAllCalled} and return C{self.selectables}.
         """
@@ -175,10 +178,10 @@ class StubErrorReporter:
         L{addError}.
     """
 
-    def __init__(self):
-        self.errors = []
+    def __init__(self) -> None:
+        self.errors: list[tuple[object, Failure]] = []
 
-    def addError(self, test, error):
+    def addError(self, test: object, error: Failure) -> None:
         """
         Record parameters in C{self.errors}.
         """
@@ -190,7 +193,7 @@ class JanitorTests(SynchronousTestCase):
     Tests for L{_Janitor}!
     """
 
-    def test_cleanPendingSpinsReactor(self):
+    def test_cleanPendingSpinsReactor(self) -> None:
         """
         During pending-call cleanup, the reactor will be spun twice with an
         instant timeout. This is not a requirement, it is only a test for
@@ -202,22 +205,22 @@ def test_cleanPendingSpinsReactor(self):
         jan._cleanPending()
         self.assertEqual(reactor.iterations, [0, 0])
 
-    def test_cleanPendingCancelsCalls(self):
+    def test_cleanPendingCancelsCalls(self) -> None:
         """
         During pending-call cleanup, the janitor cancels pending timed calls.
         """
 
-        def func():
+        def func() -> str:
             return "Lulz"
 
-        cancelled = []
+        cancelled: list[DelayedCall] = []
         delayedCall = DelayedCall(300, func, (), {}, cancelled.append, lambda x: None)
         reactor = StubReactor([delayedCall])
         jan = _Janitor(None, None, reactor=reactor)
         jan._cleanPending()
         self.assertEqual(cancelled, [delayedCall])
 
-    def test_cleanPendingReturnsDelayedCallStrings(self):
+    def test_cleanPendingReturnsDelayedCallStrings(self) -> None:
         """
         The Janitor produces string representations of delayed calls from the
         delayed call cleanup method. It gets the string representations
@@ -234,7 +237,7 @@ def test_cleanPendingReturnsDelayedCallStrings(self):
         strings = jan._cleanPending()
         self.assertEqual(strings, [delayedCallString])
 
-    def test_cleanReactorRemovesSelectables(self):
+    def test_cleanReactorRemovesSelectables(self) -> None:
         """
         The Janitor will remove selectables during reactor cleanup.
         """
@@ -243,22 +246,22 @@ def test_cleanReactorRemovesSelectables(self):
         jan._cleanReactor()
         self.assertEqual(reactor.removeAllCalled, 1)
 
-    def test_cleanReactorKillsProcesses(self):
+    def test_cleanReactorKillsProcesses(self) -> None:
         """
         The Janitor will kill processes during reactor cleanup.
         """
 
         @implementer(IProcessTransport)
-        class StubProcessTransport:
+        class StubProcessTransport:  # type: ignore[misc]
             """
             A stub L{IProcessTransport} provider which records signals.
             @ivar signals: The signals passed to L{signalProcess}.
             """
 
-            def __init__(self):
-                self.signals = []
+            def __init__(self) -> None:
+                self.signals: list[str | int] = []
 
-            def signalProcess(self, signal):
+            def signalProcess(self, signal: str | int) -> None:
                 """
                 Append C{signal} to C{self.signals}.
                 """
@@ -270,7 +273,7 @@ def signalProcess(self, signal):
         jan._cleanReactor()
         self.assertEqual(pt.signals, ["KILL"])
 
-    def test_cleanReactorReturnsSelectableStrings(self):
+    def test_cleanReactorReturnsSelectableStrings(self) -> None:
         """
         The Janitor returns string representations of the selectables that it
         cleaned up from the reactor cleanup method.
@@ -289,7 +292,7 @@ def __repr__(self) -> str:
         jan = _Janitor(None, None, reactor=reactor)
         self.assertEqual(jan._cleanReactor(), ["(SELECTABLE!)"])
 
-    def test_postCaseCleanupNoErrors(self):
+    def test_postCaseCleanupNoErrors(self) -> None:
         """
         The post-case cleanup method will return True and not call C{addError}
         on the result if there are no pending calls.
@@ -301,7 +304,7 @@ def test_postCaseCleanupNoErrors(self):
         self.assertTrue(jan.postCaseCleanup())
         self.assertEqual(reporter.errors, [])
 
-    def test_postCaseCleanupWithErrors(self):
+    def test_postCaseCleanupWithErrors(self) -> None:
         """
         The post-case cleanup method will return False and call C{addError} on
         the result with a L{DirtyReactorAggregateError} Failure if there are
@@ -319,7 +322,7 @@ def test_postCaseCleanupWithErrors(self):
         self.assertEqual(len(reporter.errors), 1)
         self.assertEqual(reporter.errors[0][1].value.delayedCalls, [delayedCallString])
 
-    def test_postClassCleanupNoErrors(self):
+    def test_postClassCleanupNoErrors(self) -> None:
         """
         The post-class cleanup method will not call C{addError} on the result
         if there are no pending calls or selectables.
@@ -331,7 +334,7 @@ def test_postClassCleanupNoErrors(self):
         jan.postClassCleanup()
         self.assertEqual(reporter.errors, [])
 
-    def test_postClassCleanupWithPendingCallErrors(self):
+    def test_postClassCleanupWithPendingCallErrors(self) -> None:
         """
         The post-class cleanup method call C{addError} on the result with a
         L{DirtyReactorAggregateError} Failure if there are pending calls.
@@ -348,7 +351,7 @@ def test_postClassCleanupWithPendingCallErrors(self):
         self.assertEqual(len(reporter.errors), 1)
         self.assertEqual(reporter.errors[0][1].value.delayedCalls, [delayedCallString])
 
-    def test_postClassCleanupWithSelectableErrors(self):
+    def test_postClassCleanupWithSelectableErrors(self) -> None:
         """
         The post-class cleanup method call C{addError} on the result with a
         L{DirtyReactorAggregateError} Failure if there are selectables.
@@ -368,7 +371,7 @@ class RemoveSafelyTests(SynchronousTestCase):
     Tests for L{util._removeSafely}.
     """
 
-    def test_removeSafelyNoTrialMarker(self):
+    def test_removeSafelyNoTrialMarker(self) -> None:
         """
         If a path doesn't contain a node named C{"_trial_marker"}, that path is
         not removed by L{util._removeSafely} and a L{util._NoTrialMarker}
@@ -379,14 +382,14 @@ def test_removeSafelyNoTrialMarker(self):
         dirPath = filepath.FilePath(directory)
         self.assertRaises(util._NoTrialMarker, util._removeSafely, dirPath)
 
-    def test_removeSafelyRemoveFailsMoveSucceeds(self):
+    def test_removeSafelyRemoveFailsMoveSucceeds(self) -> None:
         """
         If an L{OSError} is raised while removing a path in
         L{util._removeSafely}, an attempt is made to move the path to a new
         name.
         """
 
-        def dummyRemove():
+        def dummyRemove() -> None:
             """
             Raise an C{OSError} to emulate the branch of L{util._removeSafely}
             in which path removal fails.
@@ -403,26 +406,26 @@ def dummyRemove():
         dirPath = filepath.FilePath(directory)
         dirPath.child(b"_trial_marker").touch()
         # Ensure that path.remove() raises an OSError
-        dirPath.remove = dummyRemove
+        dirPath.remove = dummyRemove  # type: ignore[method-assign]
 
         util._removeSafely(dirPath)
         self.assertIn("could not remove FilePath", out.getvalue())
 
-    def test_removeSafelyRemoveFailsMoveFails(self):
+    def test_removeSafelyRemoveFailsMoveFails(self) -> None:
         """
         If an L{OSError} is raised while removing a path in
         L{util._removeSafely}, an attempt is made to move the path to a new
         name. If that attempt fails, the L{OSError} is re-raised.
         """
 
-        def dummyRemove():
+        def dummyRemove() -> None:
             """
             Raise an C{OSError} to emulate the branch of L{util._removeSafely}
             in which path removal fails.
             """
             raise OSError("path removal failed")
 
-        def dummyMoveTo(path):
+        def dummyMoveTo(destination: object, followLinks: bool = True) -> None:
             """
             Raise an C{OSError} to emulate the branch of L{util._removeSafely}
             in which path movement fails.
@@ -440,8 +443,8 @@ def dummyMoveTo(path):
         dirPath.child(b"_trial_marker").touch()
 
         # Ensure that path.remove() and path.moveTo() both raise OSErrors
-        dirPath.remove = dummyRemove
-        dirPath.moveTo = dummyMoveTo
+        dirPath.remove = dummyRemove  # type: ignore[method-assign]
+        dirPath.moveTo = dummyMoveTo  # type: ignore[method-assign]
 
         error = self.assertRaises(OSError, util._removeSafely, dirPath)
         self.assertEqual(str(error), "path movement failed")
@@ -453,7 +456,7 @@ class ExcInfoTests(SynchronousTestCase):
     Tests for L{excInfoOrFailureToExcInfo}.
     """
 
-    def test_excInfo(self):
+    def test_excInfo(self) -> None:
         """
         L{excInfoOrFailureToExcInfo} returns exactly what it is passed, if it is
         passed a tuple like the one returned by L{sys.exc_info}.
@@ -461,7 +464,7 @@ def test_excInfo(self):
         info = (ValueError, ValueError("foo"), None)
         self.assertTrue(info is excInfoOrFailureToExcInfo(info))
 
-    def test_failure(self):
+    def test_failure(self) -> None:
         """
         When called with a L{Failure} instance, L{excInfoOrFailureToExcInfo}
         returns a tuple like the one returned by L{sys.exc_info}, with the
@@ -479,7 +482,7 @@ class AcquireAttributeTests(SynchronousTestCase):
     Tests for L{acquireAttribute}.
     """
 
-    def test_foundOnEarlierObject(self):
+    def test_foundOnEarlierObject(self) -> None:
         """
         The value returned by L{acquireAttribute} is the value of the requested
         attribute on the first object in the list passed in which has that
@@ -488,7 +491,7 @@ def test_foundOnEarlierObject(self):
         self.value = value = object()
         self.assertTrue(value is acquireAttribute([self, object()], "value"))
 
-    def test_foundOnLaterObject(self):
+    def test_foundOnLaterObject(self) -> None:
         """
         The same as L{test_foundOnEarlierObject}, but for the case where the 2nd
         element in the object list has the attribute and the first does not.
@@ -496,14 +499,14 @@ def test_foundOnLaterObject(self):
         self.value = value = object()
         self.assertTrue(value is acquireAttribute([object(), self], "value"))
 
-    def test_notFoundException(self):
+    def test_notFoundException(self) -> None:
         """
         If none of the objects passed in the list to L{acquireAttribute} have
         the requested attribute, L{AttributeError} is raised.
         """
         self.assertRaises(AttributeError, acquireAttribute, [object()], "foo")
 
-    def test_notFoundDefault(self):
+    def test_notFoundDefault(self) -> None:
         """
         If none of the objects passed in the list to L{acquireAttribute} have
         the requested attribute and a default value is given, the default value
@@ -520,16 +523,16 @@ class ListToPhraseTests(SynchronousTestCase):
     two being separated by a final delimiter.
     """
 
-    def test_empty(self):
+    def test_empty(self) -> None:
         """
         If things is empty, an empty string is returned.
         """
-        sample = []
+        sample: list[None] = []
         expected = ""
         result = util._listToPhrase(sample, "and")
         self.assertEqual(expected, result)
 
-    def test_oneWord(self):
+    def test_oneWord(self) -> None:
         """
         With a single item, the item is returned.
         """
@@ -538,7 +541,7 @@ def test_oneWord(self):
         result = util._listToPhrase(sample, "and")
         self.assertEqual(expected, result)
 
-    def test_twoWords(self):
+    def test_twoWords(self) -> None:
         """
         Two words are separated by the final delimiter.
         """
@@ -547,7 +550,7 @@ def test_twoWords(self):
         result = util._listToPhrase(sample, "and")
         self.assertEqual(expected, result)
 
-    def test_threeWords(self):
+    def test_threeWords(self) -> None:
         """
         With more than two words, the first two are separated by the delimiter.
         """
@@ -556,7 +559,7 @@ def test_threeWords(self):
         result = util._listToPhrase(sample, "and")
         self.assertEqual(expected, result)
 
-    def test_fourWords(self):
+    def test_fourWords(self) -> None:
         """
         If a delimiter is specified, it is used instead of the default comma.
         """
@@ -565,7 +568,7 @@ def test_fourWords(self):
         result = util._listToPhrase(sample, "or", delimiter="; ")
         self.assertEqual(expected, result)
 
-    def test_notString(self):
+    def test_notString(self) -> None:
         """
         If something in things is not a string, it is converted into one.
         """
@@ -574,7 +577,7 @@ def test_notString(self):
         result = util._listToPhrase(sample, "and")
         self.assertEqual(expected, result)
 
-    def test_stringTypeError(self):
+    def test_stringTypeError(self) -> None:
         """
         If things is a string, a TypeError is raised.
         """
@@ -582,7 +585,7 @@ def test_stringTypeError(self):
         error = self.assertRaises(TypeError, util._listToPhrase, sample, "and")
         self.assertEqual(str(error), "Things must be a list or a tuple")
 
-    def test_iteratorTypeError(self):
+    def test_iteratorTypeError(self) -> None:
         """
         If things is an iterator, a TypeError is raised.
         """
@@ -590,12 +593,12 @@ def test_iteratorTypeError(self):
         error = self.assertRaises(TypeError, util._listToPhrase, sample, "and")
         self.assertEqual(str(error), "Things must be a list or a tuple")
 
-    def test_generatorTypeError(self):
+    def test_generatorTypeError(self) -> None:
         """
         If things is a generator, a TypeError is raised.
         """
 
-        def sample():
+        def sample() -> Generator[int, None, None]:
             yield from range(2)
 
         error = self.assertRaises(TypeError, util._listToPhrase, sample, "and")
@@ -607,7 +610,7 @@ class OpenTestLogTests(SynchronousTestCase):
     Tests for C{openTestLog}.
     """
 
-    def test_utf8(self):
+    def test_utf8(self) -> None:
         """
         The log file is opened in text mode and uses UTF-8 for encoding.
         """
@@ -629,7 +632,7 @@ def test_utf8(self):
 
         assert_that(text.encode("utf-8"), equal_to(written))
 
-    def test_append(self):
+    def test_append(self) -> None:
         """
         The log file is opened in append mode so if runner configuration specifies
         an existing log file its contents are not wiped out.
diff --git a/src/twisted/trial/test/test_warning.py b/src/twisted/trial/test/test_warning.py
--- a/src/twisted/trial/test/test_warning.py
+++ b/src/twisted/trial/test/test_warning.py
@@ -4,15 +4,20 @@
 """
 Tests for Trial's interaction with the Python warning system.
 """
-
+from __future__ import annotations
 
 import sys
 import warnings
 from io import StringIO
+from typing import Mapping, Sequence, TypeVar
 from unittest import TestResult
 
 from twisted.python.filepath import FilePath
-from twisted.trial._synctest import _collectWarnings, _setWarningRegistryToNone
+from twisted.trial._synctest import (
+    _collectWarnings,
+    _setWarningRegistryToNone,
+    _Warning,
+)
 from twisted.trial.unittest import SynchronousTestCase
 
 
@@ -28,15 +33,15 @@ class MockTests(SynchronousTestCase):
         """
 
         message = "some warning text"
-        category = UserWarning
+        category: type[Warning] = UserWarning
 
-        def test_unflushed(self):
+        def test_unflushed(self) -> None:
             """
             Generate a warning and don't flush it.
             """
             warnings.warn(self.message, self.category)
 
-        def test_flushed(self):
+        def test_flushed(self) -> None:
             """
             Generate a warning and flush it.
             """
@@ -44,13 +49,17 @@ def test_flushed(self):
             self.assertEqual(len(self.flushWarnings()), 1)
 
 
+_K = TypeVar("_K")
+_V = TypeVar("_V")
+
+
 class FlushWarningsTests(SynchronousTestCase):
     """
     Tests for C{flushWarnings}, an API for examining the warnings
     emitted so far in a test.
     """
 
-    def assertDictSubset(self, set, subset):
+    def assertDictSubset(self, set: Mapping[_K, _V], subset: Mapping[_K, _V]) -> None:
         """
         Assert that all the keys present in C{subset} are also present in
         C{set} and that the corresponding values are equal.
@@ -58,7 +67,9 @@ def assertDictSubset(self, set, subset):
         for k, v in subset.items():
             self.assertEqual(set[k], v)
 
-    def assertDictSubsets(self, sets, subsets):
+    def assertDictSubsets(
+        self, sets: Sequence[Mapping[_K, _V]], subsets: Sequence[Mapping[_K, _V]]
+    ) -> None:
         """
         For each pair of corresponding elements in C{sets} and C{subsets},
         assert that the element from C{subsets} is a subset of the element from
@@ -68,14 +79,14 @@ def assertDictSubsets(self, sets, subsets):
         for a, b in zip(sets, subsets):
             self.assertDictSubset(a, b)
 
-    def test_none(self):
+    def test_none(self) -> None:
         """
         If no warnings are emitted by a test, C{flushWarnings} returns an empty
         list.
         """
         self.assertEqual(self.flushWarnings(), [])
 
-    def test_several(self):
+    def test_several(self) -> None:
         """
         If several warnings are emitted by a test, C{flushWarnings} returns a
         list containing all of them.
@@ -96,7 +107,7 @@ def test_several(self):
             ],
         )
 
-    def test_repeated(self):
+    def test_repeated(self) -> None:
         """
         The same warning triggered twice from the same place is included twice
         in the list returned by C{flushWarnings}.
@@ -110,7 +121,7 @@ def test_repeated(self):
             self.flushWarnings(), [{"category": category, "message": message}] * 2
         )
 
-    def test_cleared(self):
+    def test_cleared(self) -> None:
         """
         After a particular warning event has been returned by C{flushWarnings},
         it is not returned by subsequent calls.
@@ -123,7 +134,7 @@ def test_cleared(self):
         )
         self.assertEqual(self.flushWarnings(), [])
 
-    def test_unflushed(self):
+    def test_unflushed(self) -> None:
         """
         Any warnings emitted by a test which are not flushed are emitted to the
         Python warning system.
@@ -146,7 +157,7 @@ def test_unflushed(self):
 
         self.assertEqual(len(warningsShown), 1)
 
-    def test_flushed(self):
+    def test_flushed(self) -> None:
         """
         Any warnings emitted by a test which are flushed are not emitted to the
         Python warning system.
@@ -159,7 +170,7 @@ def test_flushed(self):
         monkey.restore()
         self.assertEqual(output.getvalue(), "")
 
-    def test_warningsConfiguredAsErrors(self):
+    def test_warningsConfiguredAsErrors(self) -> None:
         """
         If a warnings filter has been installed which turns warnings into
         exceptions, tests have an error added to the reporter for them for each
@@ -187,9 +198,9 @@ class CustomWarning(Warning):
                 .endswith("CustomWarning: some warning text")
             )
         finally:
-            warnings.filters[:] = originalWarnings
+            warnings.filters[:] = originalWarnings  # type: ignore[index]
 
-    def test_flushedWarningsConfiguredAsErrors(self):
+    def test_flushedWarningsConfiguredAsErrors(self) -> None:
         """
         If a warnings filter has been installed which turns warnings into
         exceptions, tests which emit those warnings but flush them do not have
@@ -209,9 +220,9 @@ class CustomWarning(Warning):
             case.run(result)
             self.assertEqual(result.errors, [])
         finally:
-            warnings.filters[:] = originalWarnings
+            warnings.filters[:] = originalWarnings  # type: ignore[index]
 
-    def test_multipleFlushes(self):
+    def test_multipleFlushes(self) -> None:
         """
         Any warnings emitted after a call to C{flushWarnings} can be flushed by
         another call to C{flushWarnings}.
@@ -221,7 +232,7 @@ def test_multipleFlushes(self):
         warnings.warn("second message")
         self.assertEqual(len(self.flushWarnings()), 1)
 
-    def test_filterOnOffendingFunction(self):
+    def test_filterOnOffendingFunction(self) -> None:
         """
         The list returned by C{flushWarnings} includes only those
         warnings which refer to the source of the function passed as the value
@@ -230,13 +241,13 @@ def test_filterOnOffendingFunction(self):
         firstMessage = "first warning text"
         firstCategory = UserWarning
 
-        def one():
+        def one() -> None:
             warnings.warn(firstMessage, firstCategory, stacklevel=1)
 
         secondMessage = "some text"
         secondCategory = RuntimeWarning
 
-        def two():
+        def two() -> None:
             warnings.warn(secondMessage, secondCategory, stacklevel=1)
 
         one()
@@ -251,13 +262,13 @@ def two():
             [{"category": secondCategory, "message": secondMessage}],
         )
 
-    def test_functionBoundaries(self):
+    def test_functionBoundaries(self) -> None:
         """
         Verify that warnings emitted at the very edges of a function are still
         determined to be emitted from that function.
         """
 
-        def warner():
+        def warner() -> None:
             warnings.warn("first line warning")
             warnings.warn("internal line warning")
             warnings.warn("last line warning")
@@ -265,7 +276,7 @@ def warner():
         warner()
         self.assertEqual(len(self.flushWarnings(offendingFunctions=[warner])), 3)
 
-    def test_invalidFilter(self):
+    def test_invalidFilter(self) -> None:
         """
         If an object which is neither a function nor a method is included in the
         C{offendingFunctions} list, C{flushWarnings} raises L{ValueError}.  Such
@@ -275,7 +286,7 @@ def test_invalidFilter(self):
         self.assertRaises(ValueError, self.flushWarnings, [None])
         self.assertEqual(len(self.flushWarnings()), 1)
 
-    def test_missingSource(self):
+    def test_missingSource(self) -> None:
         """
         Warnings emitted by a function the source code of which is not
         available can still be flushed.
@@ -304,7 +315,7 @@ def foo():
         missingsourcefile.foo()
         self.assertEqual(len(self.flushWarnings([missingsourcefile.foo])), 1)
 
-    def test_renamedSource(self):
+    def test_renamedSource(self) -> None:
         """
         Warnings emitted by a function defined in a file which has been renamed
         since it was initially compiled can still be flushed.
@@ -364,7 +375,7 @@ def foo():
         # Flush it
         self.assertEqual(len(self.flushWarnings([module.foo])), 1)
 
-    def test_offendingFunctions_deep_branch(self):
+    def test_offendingFunctions_deep_branch(self) -> None:
         """
         In Python 3.6 the dis.findlinestarts documented behaviour
         was changed such that the reported lines might not be sorted ascending.
@@ -374,7 +385,7 @@ def test_offendingFunctions_deep_branch(self):
         associated with any warnings.
         """
 
-        def foo(a=1, b=1):
+        def foo(a: int = 1, b: int = 1) -> None:
             if a:
                 if b:
                     warnings.warn("oh no")
@@ -397,16 +408,16 @@ class CollectWarningsTests(SynchronousTestCase):
     Tests for L{_collectWarnings}.
     """
 
-    def test_callsObserver(self):
+    def test_callsObserver(self) -> None:
         """
         L{_collectWarnings} calls the observer with each emitted warning.
         """
         firstMessage = "dummy calls observer warning"
         secondMessage = firstMessage[::-1]
         thirdMessage = Warning(1, 2, 3)
-        events = []
+        events: list[str | _Warning] = []
 
-        def f():
+        def f() -> None:
             events.append("call")
             warnings.warn(firstMessage)
             warnings.warn(secondMessage)
@@ -416,13 +427,16 @@ def f():
         _collectWarnings(events.append, f)
 
         self.assertEqual(events[0], "call")
+        assert isinstance(events[1], _Warning)
         self.assertEqual(events[1].message, firstMessage)
+        assert isinstance(events[2], _Warning)
         self.assertEqual(events[2].message, secondMessage)
+        assert isinstance(events[3], _Warning)
         self.assertEqual(events[3].message, str(thirdMessage))
         self.assertEqual(events[4], "returning")
         self.assertEqual(len(events), 5)
 
-    def test_suppresses(self):
+    def test_suppresses(self) -> None:
         """
         Any warnings emitted by a call to a function passed to
         L{_collectWarnings} are not actually emitted to the warning system.
@@ -432,7 +446,7 @@ def test_suppresses(self):
         _collectWarnings(lambda x: None, warnings.warn, "text")
         self.assertEqual(output.getvalue(), "")
 
-    def test_callsFunction(self):
+    def test_callsFunction(self) -> None:
         """
         L{_collectWarnings} returns the result of calling the callable passed to
         it with the parameters given.
@@ -440,7 +454,7 @@ def test_callsFunction(self):
         arguments = []
         value = object()
 
-        def f(*args, **kwargs):
+        def f(*args: object, **kwargs: object) -> object:
             arguments.append((args, kwargs))
             return value
 
@@ -448,7 +462,7 @@ def f(*args, **kwargs):
         self.assertEqual(arguments, [((1, "a"), {"b": 2, "c": "d"})])
         self.assertIdentical(result, value)
 
-    def test_duplicateWarningCollected(self):
+    def test_duplicateWarningCollected(self) -> None:
         """
         Subsequent emissions of a warning from a particular source site can be
         collected by L{_collectWarnings}.  In particular, the per-module
@@ -460,31 +474,31 @@ def test_duplicateWarningCollected(self):
         # to ensure that even modules which are first imported as a test is
         # running still interact properly with the warning system.
         global __warningregistry__
-        del __warningregistry__
+        del __warningregistry__  # type: ignore[name-defined]
 
-        def f():
+        def f() -> None:
             warnings.warn("foo")
 
         warnings.simplefilter("default")
         f()
-        events = []
+        events: list[_Warning] = []
         _collectWarnings(events.append, f)
         self.assertEqual(len(events), 1)
         self.assertEqual(events[0].message, "foo")
         self.assertEqual(len(self.flushWarnings()), 1)
 
-    def test_immutableObject(self):
+    def test_immutableObject(self) -> None:
         """
         L{_collectWarnings}'s behavior is not altered by the presence of an
         object which cannot have attributes set on it as a value in
         C{sys.modules}.
         """
         key = object()
-        sys.modules[key] = key
-        self.addCleanup(sys.modules.pop, key)
+        sys.modules[key] = key  # type: ignore[index, assignment]
+        self.addCleanup(sys.modules.pop, key)  # type: ignore[arg-type]
         self.test_duplicateWarningCollected()
 
-    def test_setWarningRegistryChangeWhileIterating(self):
+    def test_setWarningRegistryChangeWhileIterating(self) -> None:
         """
         If the dictionary passed to L{_setWarningRegistryToNone} changes size
         partway through the process, C{_setWarningRegistryToNone} continues to
@@ -495,14 +509,14 @@ def test_setWarningRegistryChangeWhileIterating(self):
         really a module and imports things on setattr.  py.test does this, as
         does L{twisted.python.deprecate.deprecatedModuleAttribute}.
         """
-        d = {}
+        d: dict[object, A | None] = {}
 
         class A:
-            def __init__(self, key):
+            def __init__(self, key: object) -> None:
                 self.__dict__["_key"] = key
 
-            def __setattr__(self, value, item):
-                d[self._key] = None
+            def __setattr__(self, value: object, item: object) -> None:
+                d[self._key] = None  # type: ignore[attr-defined]
 
         key1 = object()
         key2 = object()
diff --git a/src/twisted/web/test/test_xml.py b/src/twisted/web/test/test_xml.py
--- a/src/twisted/web/test/test_xml.py
+++ b/src/twisted/web/test/test_xml.py
@@ -5,29 +5,35 @@
 """
 Some fairly inadequate testcases for Twisted XML support.
 """
+from __future__ import annotations
+
 from importlib import reload
 from io import BytesIO
 
+from typing_extensions import Literal
+
 from twisted.trial.unittest import TestCase
 from twisted.web import domhelpers, microdom, sux
 
 
 class Sux0r(sux.XMLParser):
-    def __init__(self):
-        self.tokens = []
+    def __init__(self) -> None:
+        self.tokens: list[
+            tuple[Literal["start"], str, dict[str, str]] | tuple[Literal["text"], str]
+        ] = []
 
-    def getTagStarts(self):
+    def getTagStarts(self) -> list[tuple[Literal["start"], str, dict[str, str]]]:
         return [token for token in self.tokens if token[0] == "start"]
 
-    def gotTagStart(self, name, attrs):
+    def gotTagStart(self, name: str, attrs: dict[str, str]) -> None:
         self.tokens.append(("start", name, attrs))
 
-    def gotText(self, text):
+    def gotText(self, text: str) -> None:
         self.tokens.append(("text", text))
 
 
 class SUXTests(TestCase):
-    def test_bork(self):
+    def test_bork(self) -> None:
         s = b"<bork><bork><bork>"
         ms = Sux0r()
         ms.connectionMade()
@@ -36,7 +42,7 @@ def test_bork(self):
 
 
 class MicroDOMTests(TestCase):
-    def test_leadingTextDropping(self):
+    def test_leadingTextDropping(self) -> None:
         """
         Make sure that if there's no top-level node lenient-mode won't
         drop leading text that's outside of any elements.
@@ -52,7 +58,7 @@ def test_leadingTextDropping(self):
             byteStream.getvalue(), b"<html>Hi orders! <br />Well. <br /></html>"
         )
 
-    def test_trailingTextDropping(self):
+    def test_trailingTextDropping(self) -> None:
         """
         Ensure that no *trailing* text in a mal-formed
         no-top-level-element document(s) will not be dropped.
@@ -64,7 +70,7 @@ def test_trailingTextDropping(self):
         d.firstChild().writexml(byteStream, "", "", "", "", {}, "")
         self.assertEqual(byteStream.getvalue(), b"<html><br />Hi orders!</html>")
 
-    def test_noTags(self):
+    def test_noTags(self) -> None:
         """
         A string with nothing that looks like a tag at all should just
         be parsed as body text.
@@ -73,7 +79,7 @@ def test_noTags(self):
         d = microdom.parseString(s, beExtremelyLenient=True)
         self.assertEqual(d.firstChild().toxml(), "<html>Hi orders!</html>")
 
-    def test_surroundingCrap(self):
+    def test_surroundingCrap(self) -> None:
         """
         If a document is surrounded by non-xml text, the text should
         be remain in the XML.
@@ -82,7 +88,7 @@ def test_surroundingCrap(self):
         d = microdom.parseString(s, beExtremelyLenient=True)
         self.assertEqual(d.firstChild().toxml(), "<html>Hi<br /> orders!</html>")
 
-    def test_caseSensitiveSoonCloser(self):
+    def test_caseSensitiveSoonCloser(self) -> None:
         s = """
               <HTML><BODY>
               <P ALIGN="CENTER">
@@ -100,7 +106,7 @@ def test_caseSensitiveSoonCloser(self):
         n = domhelpers.gatherTextNodes(l[0], 1).replace("&nbsp;", " ")
         self.assertEqual(n.find("insane"), -1)
 
-    def test_lenientParenting(self):
+    def test_lenientParenting(self) -> None:
         """
         Test that C{parentNode} attributes are set to meaningful values when
         we are parsing HTML that lacks a root node.
@@ -112,7 +118,7 @@ def test_lenientParenting(self):
             d.documentElement, d.documentElement.firstChild().parentNode
         )
 
-    def test_lenientParentSingle(self):
+    def test_lenientParentSingle(self) -> None:
         """
         Test that the C{parentNode} attribute is set to a meaningful value
         when we parse an HTML document that has a non-Element root node.
@@ -123,7 +129,7 @@ def test_lenientParentSingle(self):
             d.documentElement, d.documentElement.firstChild().parentNode
         )
 
-    def test_unEntities(self):
+    def test_unEntities(self) -> None:
         s = """
                 <HTML>
                     This HTML goes between Stupid <=CrAzY!=> Dumb.
@@ -133,10 +139,10 @@ def test_unEntities(self):
         n = domhelpers.gatherTextNodes(d)
         self.assertNotEqual(n.find(">"), -1)
 
-    def test_emptyError(self):
+    def test_emptyError(self) -> None:
         self.assertRaises(sux.ParseError, microdom.parseString, "")
 
-    def test_tameDocument(self):
+    def test_tameDocument(self) -> None:
         s = """
         <test>
          <it>
@@ -151,7 +157,7 @@ def test_tameDocument(self):
         d = microdom.parseString(s)
         self.assertEqual(domhelpers.gatherTextNodes(d.documentElement).strip(), "test")
 
-    def test_awfulTagSoup(self):
+    def test_awfulTagSoup(self) -> None:
         s = """
         <html>
         <head><title> I send you this message to have your advice!!!!</titl e
@@ -177,7 +183,7 @@ def test_awfulTagSoup(self):
         l = domhelpers.findNodesNamed(d.documentElement, "blink")
         self.assertEqual(len(l), 1)
 
-    def test_scriptLeniency(self):
+    def test_scriptLeniency(self) -> None:
         s = """
         <script>(foo < bar) and (bar > foo)</script>
         <script language="javascript">foo </scrip bar </script>
@@ -194,7 +200,7 @@ def test_scriptLeniency(self):
             "foo </scrip bar ",
         )
 
-    def test_scriptLeniencyIntelligence(self):
+    def test_scriptLeniencyIntelligence(self) -> None:
         # if there is comment or CDATA in script, the autoquoting in bEL mode
         # should not happen
         s = """<script><!-- lalal --></script>"""
@@ -212,7 +218,7 @@ def test_scriptLeniencyIntelligence(self):
             microdom.parseString(s, beExtremelyLenient=1).firstChild().toxml(), s
         )
 
-    def test_preserveCase(self):
+    def test_preserveCase(self) -> None:
         s = "<eNcApSuLaTe><sUxor></sUxor><bOrk><w00T>TeXt</W00t></BoRk></EnCaPsUlAtE>"
         s2 = s.lower().replace("text", "TeXt")
         # these are the only two option permutations that *can* parse the above
@@ -236,25 +242,25 @@ def test_preserveCase(self):
         self.assertTrue(d3.isEqualToDocument(d4), f"{d3.toxml()!r} != {d4.toxml()!r}")
         self.assertTrue(d4.isEqualToDocument(d5), f"{d4.toxml()!r} != {d5.toxml()!r}")
 
-    def test_differentQuotes(self):
+    def test_differentQuotes(self) -> None:
         s = "<test a=\"a\" b='b' />"
         d = microdom.parseString(s)
         e = d.documentElement
         self.assertEqual(e.getAttribute("a"), "a")
         self.assertEqual(e.getAttribute("b"), "b")
 
-    def test_Linebreaks(self):
+    def test_Linebreaks(self) -> None:
         s = '<test \na="a"\n\tb="#b" />'
         d = microdom.parseString(s)
         e = d.documentElement
         self.assertEqual(e.getAttribute("a"), "a")
         self.assertEqual(e.getAttribute("b"), "#b")
 
-    def test_mismatchedTags(self):
+    def test_mismatchedTags(self) -> None:
         for s in "<test>", "<test> </tset>", "</test>":
             self.assertRaises(microdom.MismatchedTags, microdom.parseString, s)
 
-    def test_comment(self):
+    def test_comment(self) -> None:
         s = "<bar><!--<foo />--></bar>"
         d = microdom.parseString(s)
         e = d.documentElement
@@ -266,7 +272,7 @@ def test_comment(self):
         self.assertTrue(c is not c2)
         self.assertEqual(c2.toxml(), "<!--<foo />-->")
 
-    def test_text(self):
+    def test_text(self) -> None:
         d = microdom.parseString("<bar>xxxx</bar>").documentElement
         text = d.childNodes[0]
         self.assertTrue(isinstance(text, microdom.Text))
@@ -275,7 +281,7 @@ def test_text(self):
         self.assertTrue(clone is not text)
         self.assertEqual(clone.toxml(), "xxxx")
 
-    def test_entities(self):
+    def test_entities(self) -> None:
         nodes = microdom.parseString("<b>&amp;&#12AB;</b>").documentElement.childNodes
         self.assertEqual(len(nodes), 2)
         self.assertEqual(nodes[0].data, "&amp;")
@@ -284,14 +290,14 @@ def test_entities(self):
         for n in nodes:
             self.assertTrue(isinstance(n, microdom.EntityReference))
 
-    def test_CData(self):
+    def test_CData(self) -> None:
         s = "<x><![CDATA[</x>\r\n & foo]]></x>"
         cdata = microdom.parseString(s).documentElement.childNodes[0]
         self.assertTrue(isinstance(cdata, microdom.CDATASection))
         self.assertEqual(cdata.data, "</x>\r\n & foo")
         self.assertEqual(cdata.cloneNode().toxml(), "<![CDATA[</x>\r\n & foo]]>")
 
-    def test_singletons(self):
+    def test_singletons(self) -> None:
         s = "<foo><b/><b /><b\n/></foo>"
         s2 = "<foo><b/><b/><b/></foo>"
         nodes = microdom.parseString(s).documentElement.childNodes
@@ -302,7 +308,7 @@ def test_singletons(self):
             self.assertEqual(n.nodeName, "b")
             self.assertTrue(n.isEqualToNode(n2))
 
-    def test_attributes(self):
+    def test_attributes(self) -> None:
         s = '<foo a="b" />'
         node = microdom.parseString(s).documentElement
 
@@ -316,7 +322,7 @@ def test_attributes(self):
         node.setAttribute("foo", "bar")
         self.assertEqual(node.getAttribute("foo"), "bar")
 
-    def test_children(self):
+    def test_children(self) -> None:
         s = "<foo><bar /><baz /><bax>foo</bax></foo>"
         d = microdom.parseString(s).documentElement
         self.assertEqual([n.nodeName for n in d.childNodes], ["bar", "baz", "bax"])
@@ -325,7 +331,7 @@ def test_children(self):
         self.assertTrue(d.hasChildNodes())
         self.assertTrue(not d.firstChild().hasChildNodes())
 
-    def test_mutate(self):
+    def test_mutate(self) -> None:
         s = "<foo />"
         s1 = '<foo a="b"><bar/><foo/></foo>'
         s2 = '<foo a="b">foo</foo>'
@@ -355,7 +361,7 @@ def test_mutate(self):
         self.assertEqual(d.firstChild(), t)
         self.assertTrue(d.isEqualToNode(d2))
 
-    def test_replaceNonChild(self):
+    def test_replaceNonChild(self) -> None:
         """
         L{Node.replaceChild} raises L{ValueError} if the node given to be
         replaced is not a child of the node C{replaceChild} is called on.
@@ -366,7 +372,7 @@ def test_replaceNonChild(self):
 
         self.assertRaises(ValueError, parent.replaceChild, replacement, orphan)
 
-    def test_search(self):
+    def test_search(self) -> None:
         s = "<foo><bar id='me' /><baz><foo /></baz></foo>"
         s2 = "<fOo><bAr id='me' /><bAz><fOO /></bAz></fOo>"
         d = microdom.parseString(s)
@@ -396,7 +402,7 @@ def test_search(self):
             d3.getElementsByTagName("fOo"), [root, root.lastChild().firstChild()]
         )
 
-    def test_doctype(self):
+    def test_doctype(self) -> None:
         s = (
             '<?xml version="1.0"?>'
             '<!DOCTYPE foo PUBLIC "baz" "http://www.example.com/example.dtd">'
@@ -419,7 +425,7 @@ def test_doctype(self):
         ("<foo>hello there &amp; yoyoy</foo>", "<foo>hello there &amp; yoyoy</foo>"),
     ]
 
-    def test_output(self):
+    def test_output(self) -> None:
         for s, out in self.samples:
             d = microdom.parseString(s, caseInsensitive=0)
             d2 = microdom.parseString(out, caseInsensitive=0)
@@ -427,11 +433,11 @@ def test_output(self):
             self.assertEqual(out, testOut)
             self.assertTrue(d.isEqualToDocument(d2))
 
-    def test_errors(self):
+    def test_errors(self) -> None:
         for s in ["<foo>&am</foo>", "<foo", "<f>&</f>", "<() />"]:
             self.assertRaises(Exception, microdom.parseString, s)
 
-    def test_caseInsensitive(self):
+    def test_caseInsensitive(self) -> None:
         s = "<foo a='b'><BAx>x</bax></FOO>"
         s2 = '<foo a="b"><bax>x</bax></foo>'
         s3 = "<FOO a='b'><BAx>x</BAx></FOO>"
@@ -454,7 +460,7 @@ def test_caseInsensitive(self):
         self.assertEqual(d4.documentElement.toxml(), '<foo A="b">x</foo>')
         self.assertEqual(d5.documentElement.toxml(), '<foo a="b">x</foo>')
 
-    def test_eatingWhitespace(self):
+    def test_eatingWhitespace(self) -> None:
         s = """<hello>
         </hello>"""
         d = microdom.parseString(s)
@@ -463,7 +469,7 @@ def test_eatingWhitespace(self):
         )
         self.assertTrue(d.isEqualToDocument(microdom.parseString("<hello></hello>")))
 
-    def test_lenientAmpersand(self):
+    def test_lenientAmpersand(self) -> None:
         prefix = "<?xml version='1.0'?>"
         # we use <pre> so space will be preserved
         for i, o in [
@@ -478,28 +484,28 @@ def test_lenientAmpersand(self):
         d = microdom.parseString("<t>hello & there</t>", beExtremelyLenient=1)
         self.assertEqual(d.documentElement.toxml(), "<t>hello &amp; there</t>")
 
-    def test_insensitiveLenient(self):
+    def test_insensitiveLenient(self) -> None:
         # testing issue #537
         d = microdom.parseString(
             "<?xml version='1.0'?><bar><xA><y>c</Xa> <foo></bar>", beExtremelyLenient=1
         )
         self.assertEqual(d.documentElement.firstChild().toxml(), "<xa><y>c</y></xa>")
 
-    def test_laterCloserSimple(self):
+    def test_laterCloserSimple(self) -> None:
         s = "<ul><li>foo<li>bar<li>baz</ul>"
         d = microdom.parseString(s, beExtremelyLenient=1)
         expected = "<ul><li>foo</li><li>bar</li><li>baz</li></ul>"
         actual = d.documentElement.toxml()
         self.assertEqual(expected, actual)
 
-    def test_laterCloserCaseInsensitive(self):
+    def test_laterCloserCaseInsensitive(self) -> None:
         s = "<DL><p><DT>foo<DD>bar</DL>"
         d = microdom.parseString(s, beExtremelyLenient=1)
         expected = "<dl><p></p><dt>foo</dt><dd>bar</dd></dl>"
         actual = d.documentElement.toxml()
         self.assertEqual(expected, actual)
 
-    def test_laterCloserDL(self):
+    def test_laterCloserDL(self) -> None:
         s = (
             "<dl>"
             "<dt>word<dd>definition"
@@ -516,7 +522,7 @@ def test_laterCloserDL(self):
         actual = d.documentElement.toxml()
         self.assertEqual(expected, actual)
 
-    def test_unicodeTolerance(self):
+    def test_unicodeTolerance(self) -> None:
         import struct
 
         s = "<foo><bar><baz /></bar></foo>"
@@ -534,7 +540,7 @@ def test_unicodeTolerance(self):
             b"\x00>\x00<\x00/\x00J\x00A\x00P\x00A\x00N\x00E\x00S\x00E\x00>\x00"
         )
 
-        def reverseBytes(s):
+        def reverseBytes(s: bytes) -> bytes:
             fmt = str(len(s) // 2) + "H"
             return struct.pack("<" + fmt, *struct.unpack(">" + fmt, s))
 
@@ -561,7 +567,7 @@ def reverseBytes(s):
             j3.toxml(), (hdr + "<foo><div>\u221a</div><!--\u221a--></foo>")
         )
 
-    def test_namedChildren(self):
+    def test_namedChildren(self) -> None:
         tests = {
             "<foo><bar /><bar unf='1' /><bar>asdfadsf</bar>" "<bam/></foo>": 3,
             "<foo>asdf</foo>": 0,
@@ -574,7 +580,7 @@ def test_namedChildren(self):
             if result:
                 self.assertTrue(hasattr(result[0], "tagName"))
 
-    def test_cloneNode(self):
+    def test_cloneNode(self) -> None:
         s = '<foo a="b"><bax>x</bax></foo>'
         node = microdom.parseString(s).documentElement
         clone = node.cloneNode(deep=1)
@@ -587,7 +593,7 @@ def test_cloneNode(self):
         self.assertEqual(s, clone.toxml())
         self.assertEqual(node.namespace, clone.namespace)
 
-    def test_cloneDocument(self):
+    def test_cloneDocument(self) -> None:
         s = (
             '<?xml version="1.0"?>'
             '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
@@ -603,7 +609,7 @@ def test_cloneDocument(self):
         self.assertTrue(clone.isEqualToDocument(node))
         self.assertTrue(node.isEqualToDocument(clone))
 
-    def test_LMX(self):
+    def test_LMX(self) -> None:
         n = microdom.Element("p")
         lmx = microdom.lmx(n)
         lmx.text("foo")
@@ -615,14 +621,14 @@ def test_LMX(self):
         s = '<p>foo<b a="c"><foo z="foo"></foo><foo></foo><bar c="y"></bar></b></p>'
         self.assertEqual(s, n.toxml())
 
-    def test_dict(self):
+    def test_dict(self) -> None:
         """
         Returns a dictionary which is hashable.
         """
         n = microdom.Element("p")
         hash(n)
 
-    def test_escaping(self):
+    def test_escaping(self) -> None:
         # issue 590
         raw = "&'some \"stuff\"', <what up?>"
         cooked = "&amp;'some &quot;stuff&quot;', &lt;what up?&gt;"
@@ -630,7 +636,7 @@ def test_escaping(self):
         self.assertEqual(esc1, cooked)
         self.assertEqual(microdom.unescape(esc1), raw)
 
-    def test_namespaces(self):
+    def test_namespaces(self) -> None:
         s = """
         <x xmlns="base">
         <y />
@@ -662,7 +668,7 @@ def test_namespaces(self):
             "1",
         )
 
-    def test_namespaceDelete(self):
+    def test_namespaceDelete(self) -> None:
         """
         Test that C{toxml} can support xml structures that remove namespaces.
         """
@@ -673,7 +679,7 @@ def test_namespaceDelete(self):
         s2 = microdom.parseString(s1).toxml()
         self.assertEqual(s1, s2)
 
-    def test_namespaceInheritance(self):
+    def test_namespaceInheritance(self) -> None:
         """
         Check that unspecified namespace is a thing separate from undefined
         namespace. This test added after discovering some weirdness in Lore.
@@ -686,7 +692,7 @@ def test_namespaceInheritance(self):
             parent.toxml(), '<div xmlns="http://www.w3.org/1999/xhtml"><ol></ol></div>'
         )
 
-    def test_prefixedTags(self):
+    def test_prefixedTags(self) -> None:
         """
         XML elements with a prefixed name as per upper level tag definition
         have a start-tag of C{"<prefix:tag>"} and an end-tag of
@@ -729,7 +735,7 @@ def test_prefixedTags(self):
         xmlOut = document.toxml()
         self.assertEqual(xmlOut, xmlOk)
 
-    def test_prefixPropagation(self):
+    def test_prefixPropagation(self) -> None:
         """
         Children of prefixed tags respect the default namespace at the point
         where they are rendered.  Specifically, they are not influenced by the
@@ -788,7 +794,7 @@ def test_prefixPropagation(self):
         xmlOut = document.toxml()
         self.assertEqual(xmlOut, xmlOk)
 
-    def test_deprecation(self):
+    def test_deprecation(self) -> None:
         """
         An import will raise the deprecation warning.
         """
@@ -810,7 +816,9 @@ class BrokenHTMLTests(TestCase):
     bad HTML.
     """
 
-    def checkParsed(self, input, expected, beExtremelyLenient=1):
+    def checkParsed(
+        self, input: str, expected: str, beExtremelyLenient: int = 1
+    ) -> None:
         """
         Check that C{input}, when parsed, produces a DOM where the XML
         of the document element is equal to C{expected}.
@@ -818,7 +826,7 @@ def checkParsed(self, input, expected, beExtremelyLenient=1):
         output = microdom.parseString(input, beExtremelyLenient=beExtremelyLenient)
         self.assertEqual(output.documentElement.toxml(), expected)
 
-    def test_brokenAttributeName(self):
+    def test_brokenAttributeName(self) -> None:
         """
         Check that microdom does its best to handle broken attribute names.
         The important thing is that it doesn't raise an exception.
@@ -827,7 +835,7 @@ def test_brokenAttributeName(self):
         expected = '<body><h1><div al="True" ign="center">' "Foo</div></h1></body>"
         self.checkParsed(input, expected)
 
-    def test_brokenAttributeValue(self):
+    def test_brokenAttributeValue(self) -> None:
         """
         Check that microdom encompasses broken attribute values.
         """
@@ -835,7 +843,7 @@ def test_brokenAttributeValue(self):
         expected = '<body><h1><div align="cen!\n ter">Foo</div></h1></body>'
         self.checkParsed(input, expected)
 
-    def test_brokenOpeningTag(self):
+    def test_brokenOpeningTag(self) -> None:
         """
         Check that microdom does its best to handle broken opening tags.
         The important thing is that it doesn't raise an exception.
@@ -844,7 +852,7 @@ def test_brokenOpeningTag(self):
         expected = '<body><h1><sp an="True">Hello World!</sp></h1></body>'
         self.checkParsed(input, expected)
 
-    def test_brokenSelfClosingTag(self):
+    def test_brokenSelfClosingTag(self) -> None:
         """
         Check that microdom does its best to handle broken self-closing tags
         The important thing is that it doesn't raise an exception.
@@ -852,7 +860,7 @@ def test_brokenSelfClosingTag(self):
         self.checkParsed("<body><span /!\n></body>", "<body><span></span></body>")
         self.checkParsed("<span!\n />", "<span></span>")
 
-    def test_brokenClosingTag(self):
+    def test_brokenClosingTag(self) -> None:
         """
         Check that microdom does its best to handle broken closing tags.
         The important thing is that it doesn't raise an exception.
@@ -874,7 +882,7 @@ class NodeTests(TestCase):
     Tests for L{Node}.
     """
 
-    def test_isNodeEqualTo(self):
+    def test_isNodeEqualTo(self) -> None:
         """
         L{Node.isEqualToNode} returns C{True} if and only if passed a L{Node}
         with the same children.
@@ -901,7 +909,7 @@ def test_isNodeEqualTo(self):
         another.firstChild().appendChild(microdom.Node(object()))
         self.assertTrue(node.isEqualToNode(another))
 
-    def test_validChildInstance(self):
+    def test_validChildInstance(self) -> None:
         """
         Children of L{Node} instances must also be L{Node} instances.
         """
@@ -930,7 +938,7 @@ class DocumentTests(TestCase):
 
     doctype = 'foo PUBLIC "baz" "http://www.example.com/example.dtd"'
 
-    def test_isEqualToNode(self):
+    def test_isEqualToNode(self) -> None:
         """
         L{Document.isEqualToNode} returns C{True} if and only if passed a
         L{Document} with the same C{doctype} and C{documentElement}.
@@ -961,7 +969,7 @@ def test_isEqualToNode(self):
         document.documentElement.appendChild(microdom.Node(object()))
         self.assertFalse(document.isEqualToNode(another))
 
-    def test_childRestriction(self):
+    def test_childRestriction(self) -> None:
         """
         L{Document.appendChild} raises L{ValueError} if the document already
         has a child.
@@ -978,7 +986,7 @@ class EntityReferenceTests(TestCase):
     Tests for L{EntityReference}.
     """
 
-    def test_isEqualToNode(self):
+    def test_isEqualToNode(self) -> None:
         """
         L{EntityReference.isEqualToNode} returns C{True} if and only if passed
         a L{EntityReference} with the same C{eref}.
@@ -1000,7 +1008,7 @@ class CharacterDataTests(TestCase):
     Tests for L{CharacterData}.
     """
 
-    def test_isEqualToNode(self):
+    def test_isEqualToNode(self) -> None:
         """
         L{CharacterData.isEqualToNode} returns C{True} if and only if passed a
         L{CharacterData} with the same value.
@@ -1018,7 +1026,7 @@ class CommentTests(TestCase):
     Tests for L{Comment}.
     """
 
-    def test_isEqualToNode(self):
+    def test_isEqualToNode(self) -> None:
         """
         L{Comment.isEqualToNode} returns C{True} if and only if passed a
         L{Comment} with the same value.
@@ -1032,7 +1040,7 @@ class TextTests(TestCase):
     Tests for L{Text}.
     """
 
-    def test_isEqualToNode(self):
+    def test_isEqualToNode(self) -> None:
         """
         L{Text.isEqualToNode} returns C{True} if and only if passed a L{Text}
         which represents the same data.
@@ -1055,7 +1063,7 @@ class CDATASectionTests(TestCase):
     Tests for L{CDATASection}.
     """
 
-    def test_isEqualToNode(self):
+    def test_isEqualToNode(self) -> None:
         """
         L{CDATASection.isEqualToNode} returns C{True} if and only if passed a
         L{CDATASection} which represents the same data.
@@ -1073,7 +1081,7 @@ class ElementTests(TestCase):
     Tests for L{Element}.
     """
 
-    def test_isEqualToNode(self):
+    def test_isEqualToNode(self) -> None:
         """
         L{Element.isEqualToNode} returns C{True} if and only if passed a
         L{Element} with the same C{nodeName}, C{namespace}, C{childNodes}, and