Thrift-5900: Fix Tests for Python 3.14
Client: py
Patch: Carel Combrink

This closes #3239

- Disclaimer: Claude came up with this
- Python 3.14 made files explicitly unpicklable for reasons
- The out can't be pickled in SummaryReporter
- stop in TestDispatcher is an instance method that should not be captured

Delay the imports to where they are needed

- Claude believes this is due to the way that the server is called vs the client is called and the server does not have enough time to set up completely

Attempt to fix issue with python 3.14

- Looks like python is getting more strict about scoping
- Decided to go with a local option instead of global or module variable
diff --git a/test/crossrunner/report.py b/test/crossrunner/report.py
index 9231a8b..68cfcca 100644
--- a/test/crossrunner/report.py
+++ b/test/crossrunner/report.py
@@ -240,6 +240,19 @@
         self._expected_failure = []
         self._print_header()
 
+    def __getstate__(self):
+        """Prepare object for pickling - remove unpicklable file handle (Since Python 3.14)"""
+        state = self.__dict__.copy()
+        # Remove the unpicklable file handle
+        state['out'] = None
+        return state
+
+    def __setstate__(self, state):
+        """Restore object after unpickling - restore stdout"""
+        self.__dict__.update(state)
+        # Restore stdout (since that's what it was initialized to)
+        self.out = sys.stdout
+
     @property
     def testdir(self):
         return os.path.join(self._basedir, self._testdir_rel)
diff --git a/test/crossrunner/run.py b/test/crossrunner/run.py
index e532417..c401172 100644
--- a/test/crossrunner/run.py
+++ b/test/crossrunner/run.py
@@ -374,16 +374,18 @@
             self._m = multiprocessing.managers.BaseManager()
             self._m.register('ports', PortAllocator)
             self._m.start()
-            self._pool = multiprocessing.Pool(concurrency, self._pool_init, (self._m.address,))
+            self._pool = multiprocessing.Pool(concurrency, TestDispatcher._pool_init, (self._m.address, self._stop))
         self._log.debug(
             'TestDispatcher started with %d concurrent jobs' % concurrency)
 
-    def _pool_init(self, address):
+    @staticmethod
+    def _pool_init(address, stop_event):
         global stop
         global m
         global ports
-        stop = self._stop
+        stop = stop_event
         m = multiprocessing.managers.BaseManager(address)
+        m.register('ports')
         m.connect()
         ports = m.ports()