Skip to content

Commit 5abe53f

Browse files
CJCombrinkJens-G
authored andcommitted
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
1 parent f274d98 commit 5abe53f

File tree

4 files changed

+54
-30
lines changed

4 files changed

+54
-30
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ jobs:
668668

669669
- uses: actions/setup-python@v6
670670
with:
671-
python-version: "3.13" # Pin to 3.13 for now -> see THRIFT-5900
671+
python-version: "3.x"
672672

673673
- uses: actions/setup-java@v5
674674
with:

test/crossrunner/report.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,19 @@ def __init__(self, basedir, testdir_relative, concurrent=True):
240240
self._expected_failure = []
241241
self._print_header()
242242

243+
def __getstate__(self):
244+
"""Prepare object for pickling - remove unpicklable file handle (Since Python 3.14)"""
245+
state = self.__dict__.copy()
246+
# Remove the unpicklable file handle
247+
state['out'] = None
248+
return state
249+
250+
def __setstate__(self, state):
251+
"""Restore object after unpickling - restore stdout"""
252+
self.__dict__.update(state)
253+
# Restore stdout (since that's what it was initialized to)
254+
self.out = sys.stdout
255+
243256
@property
244257
def testdir(self):
245258
return os.path.join(self._basedir, self._testdir_rel)

test/crossrunner/run.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,16 +374,18 @@ def __init__(self, testdir, basedir, logdir_rel, concurrency):
374374
self._m = multiprocessing.managers.BaseManager()
375375
self._m.register('ports', PortAllocator)
376376
self._m.start()
377-
self._pool = multiprocessing.Pool(concurrency, self._pool_init, (self._m.address,))
377+
self._pool = multiprocessing.Pool(concurrency, TestDispatcher._pool_init, (self._m.address, self._stop))
378378
self._log.debug(
379379
'TestDispatcher started with %d concurrent jobs' % concurrency)
380380

381-
def _pool_init(self, address):
381+
@staticmethod
382+
def _pool_init(address, stop_event):
382383
global stop
383384
global m
384385
global ports
385-
stop = self._stop
386+
stop = stop_event
386387
m = multiprocessing.managers.BaseManager(address)
388+
m.register('ports')
387389
m.connect()
388390
ports = m.ports()
389391

test/py/TestServer.py

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,65 +33,71 @@
3333

3434

3535
class TestHandler(object):
36+
def __init__(self, options):
37+
self.options = options
38+
3639
def testVoid(self):
37-
if options.verbose > 1:
40+
if self.options.verbose > 1:
3841
logging.info('testVoid()')
3942

4043
def testString(self, str):
41-
if options.verbose > 1:
44+
if self.options.verbose > 1:
4245
logging.info('testString(%s)' % str)
4346
return str
4447

4548
def testBool(self, boolean):
46-
if options.verbose > 1:
49+
if self.options.verbose > 1:
4750
logging.info('testBool(%s)' % str(boolean).lower())
4851
return boolean
4952

5053
def testByte(self, byte):
51-
if options.verbose > 1:
54+
if self.options.verbose > 1:
5255
logging.info('testByte(%d)' % byte)
5356
return byte
5457

5558
def testI16(self, i16):
56-
if options.verbose > 1:
59+
if self.options.verbose > 1:
5760
logging.info('testI16(%d)' % i16)
5861
return i16
5962

6063
def testI32(self, i32):
61-
if options.verbose > 1:
64+
if self.options.verbose > 1:
6265
logging.info('testI32(%d)' % i32)
6366
return i32
6467

6568
def testI64(self, i64):
66-
if options.verbose > 1:
69+
if self.options.verbose > 1:
6770
logging.info('testI64(%d)' % i64)
6871
return i64
6972

7073
def testDouble(self, dub):
71-
if options.verbose > 1:
74+
if self.options.verbose > 1:
7275
logging.info('testDouble(%f)' % dub)
7376
return dub
7477

7578
def testBinary(self, thing):
76-
if options.verbose > 1:
79+
if self.options.verbose > 1:
7780
logging.info('testBinary()') # TODO: hex output
7881
return thing
7982

8083
def testStruct(self, thing):
81-
if options.verbose > 1:
84+
if self.options.verbose > 1:
8285
logging.info('testStruct({%s, %s, %s, %s})' % (thing.string_thing, thing.byte_thing, thing.i32_thing, thing.i64_thing))
8386
return thing
8487

8588
def testException(self, arg):
86-
# if options.verbose > 1:
89+
from ThriftTest.ttypes import Xception
90+
# if self.options.verbose > 1:
8791
logging.info('testException(%s)' % arg)
8892
if arg == 'Xception':
8993
raise Xception(errorCode=1001, message=arg)
9094
elif arg == 'TException':
9195
raise TException(message='This is a TException')
9296

9397
def testMultiException(self, arg0, arg1):
94-
if options.verbose > 1:
98+
from ThriftTest.ttypes import Xtruct, Xception, Xception2
99+
100+
if self.options.verbose > 1:
95101
logging.info('testMultiException(%s, %s)' % (arg0, arg1))
96102
if arg0 == 'Xception':
97103
raise Xception(errorCode=1001, message='This is an Xception')
@@ -102,49 +108,49 @@ def testMultiException(self, arg0, arg1):
102108
return Xtruct(string_thing=arg1)
103109

104110
def testOneway(self, seconds):
105-
if options.verbose > 1:
111+
if self.options.verbose > 1:
106112
logging.info('testOneway(%d) => sleeping...' % seconds)
107113
time.sleep(seconds / 3) # be quick
108-
if options.verbose > 1:
114+
if self.options.verbose > 1:
109115
logging.info('done sleeping')
110116

111117
def testNest(self, thing):
112-
if options.verbose > 1:
118+
if self.options.verbose > 1:
113119
logging.info('testNest(%s)' % thing)
114120
return thing
115121

116122
def testMap(self, thing):
117-
if options.verbose > 1:
123+
if self.options.verbose > 1:
118124
logging.info('testMap(%s)' % thing)
119125
return thing
120126

121127
def testStringMap(self, thing):
122-
if options.verbose > 1:
128+
if self.options.verbose > 1:
123129
logging.info('testStringMap(%s)' % thing)
124130
return thing
125131

126132
def testSet(self, thing):
127-
if options.verbose > 1:
133+
if self.options.verbose > 1:
128134
logging.info('testSet(%s)' % thing)
129135
return thing
130136

131137
def testList(self, thing):
132-
if options.verbose > 1:
138+
if self.options.verbose > 1:
133139
logging.info('testList(%s)' % thing)
134140
return thing
135141

136142
def testEnum(self, thing):
137-
if options.verbose > 1:
143+
if self.options.verbose > 1:
138144
logging.info('testEnum(%s)' % thing)
139145
return thing
140146

141147
def testTypedef(self, thing):
142-
if options.verbose > 1:
148+
if self.options.verbose > 1:
143149
logging.info('testTypedef(%s)' % thing)
144150
return thing
145151

146152
def testMapMap(self, thing):
147-
if options.verbose > 1:
153+
if self.options.verbose > 1:
148154
logging.info('testMapMap(%s)' % thing)
149155
return {
150156
-4: {
@@ -162,7 +168,9 @@ def testMapMap(self, thing):
162168
}
163169

164170
def testInsanity(self, argument):
165-
if options.verbose > 1:
171+
from ThriftTest.ttypes import Insanity
172+
173+
if self.options.verbose > 1:
166174
logging.info('testInsanity(%s)' % argument)
167175
return {
168176
1: {
@@ -173,7 +181,9 @@ def testInsanity(self, argument):
173181
}
174182

175183
def testMulti(self, arg0, arg1, arg2, arg3, arg4, arg5):
176-
if options.verbose > 1:
184+
from ThriftTest.ttypes import Xtruct
185+
186+
if self.options.verbose > 1:
177187
logging.info('testMulti(%s, %s, %s, %s, %s, %s)' % (arg0, arg1, arg2, arg3, arg4, arg5))
178188
return Xtruct(string_thing='Hello2',
179189
byte_thing=arg0, i32_thing=arg1, i64_thing=arg2)
@@ -271,7 +281,7 @@ def main(options):
271281
server_type = 'THttpServer'
272282

273283
# Set up the handler and processor objects
274-
handler = TestHandler()
284+
handler = TestHandler(options)
275285
processor = ThriftTest.Processor(handler)
276286

277287
if options.proto.startswith('multi'):
@@ -397,7 +407,6 @@ def exit_gracefully(signum, frame):
397407
sys.path.insert(0, os.path.join(SCRIPT_DIR, options.genpydir))
398408

399409
from ThriftTest import ThriftTest, SecondService
400-
from ThriftTest.ttypes import Xtruct, Xception, Xception2, Insanity
401410
from thrift.Thrift import TException
402411
from thrift.TMultiplexedProcessor import TMultiplexedProcessor
403412
from thrift.transport import THeaderTransport

0 commit comments

Comments
 (0)