Skip to content

Commit 1371487

Browse files
amfarrellAndrew M. Farrell
and
Andrew M. Farrell
authored
Fix failing tests of hid.write on MacOS by pulling target functions to top of module (#1344)
Resolves #1343. On MacOS, the tests of `hid.write.ProcessWithResult` fail because the multiprocessing module tries to pickle the inlined target functions so it can then spawn child processes. To be picklable, functions need to be defined with `def` in the top level of a module[[1](https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled)]. On MacOS, multiprocessing uses spawn instead of fork[[2](python/cpython@17a5588)], so this is a problem that does not show up on Linux[[3](https://docs.python.org/3.11/library/multiprocessing.html#the-spawn-and-forkserver-start-methods)]. After moving the definitions of these functions to the top level of the module, the tests pass both on MacOS and on Linux running on CircleCI. <a data-ca-tag href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1344"><img src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review on CodeApprove" /></a> --------- Co-authored-by: Andrew M. Farrell <[email protected]>
1 parent 4ae349b commit 1371487

File tree

1 file changed

+35
-20
lines changed

1 file changed

+35
-20
lines changed

app/hid/write_test.py

+35-20
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,40 @@
55

66
import hid.write
77

8+
# Dummy functions to represent what can happen when a Human Interface Device
9+
# writes.
10+
#
11+
# On some MacOS systems, the multiprocessing module spawns rather than forks new
12+
# processes[1], which pickles these functions[2]. So, they must be defined
13+
# using `def` at the top level of a module[3].
14+
#
15+
# This was observed on a 2021 Macbook Pro M1 Max running OSX Ventura 13.2.1.
16+
#
17+
# [1] https://github.com/python/cpython/commit/17a5588740b3d126d546ad1a13bdac4e028e6d50
18+
# [2] https://docs.python.org/3.11/library/multiprocessing.html#the-spawn-and-forkserver-start-methods
19+
# [3] https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled:~:text=(using%20def%2C%20not%20lambda)
820

9-
class WriteTest(unittest.TestCase):
1021

11-
def test_process_with_result_child_completed(self):
22+
def do_nothing():
23+
pass
24+
25+
26+
def sleep_1_second():
27+
time.sleep(1)
28+
1229

13-
def target():
14-
pass
30+
def raise_exception():
31+
raise Exception('Child exception')
1532

16-
process = hid.write.ProcessWithResult(target=target, daemon=True)
33+
34+
def return_string():
35+
return 'Done!'
36+
37+
38+
class WriteTest(unittest.TestCase):
39+
40+
def test_process_with_result_child_completed(self):
41+
process = hid.write.ProcessWithResult(target=do_nothing, daemon=True)
1742
process.start()
1843
process.join()
1944
result = process.result()
@@ -22,11 +47,8 @@ def target():
2247
hid.write.ProcessResult(return_value=None, exception=None), result)
2348

2449
def test_process_with_result_child_not_completed(self):
25-
26-
def target():
27-
time.sleep(1)
28-
29-
process = hid.write.ProcessWithResult(target=target, daemon=True)
50+
process = hid.write.ProcessWithResult(target=sleep_1_second,
51+
daemon=True)
3052
process.start()
3153
# Get the result before the child process has completed.
3254
self.assertIsNone(process.result())
@@ -35,14 +57,11 @@ def target():
3557
process.kill()
3658

3759
def test_process_with_result_child_exception(self):
38-
39-
def target():
40-
raise Exception('Child exception')
41-
4260
# Silence stderr while the child exception is being raised to avoid
4361
# polluting the terminal output.
4462
with mock.patch('sys.stderr', io.StringIO()):
45-
process = hid.write.ProcessWithResult(target=target, daemon=True)
63+
process = hid.write.ProcessWithResult(target=raise_exception,
64+
daemon=True)
4665
process.start()
4766
process.join()
4867
result = process.result()
@@ -53,11 +72,7 @@ def target():
5372
self.assertEqual('Child exception', str(result.exception))
5473

5574
def test_process_with_result_return_value(self):
56-
57-
def target():
58-
return 'Done!'
59-
60-
process = hid.write.ProcessWithResult(target=target, daemon=True)
75+
process = hid.write.ProcessWithResult(target=return_string, daemon=True)
6176
process.start()
6277
process.join()
6378
result = process.result()

0 commit comments

Comments
 (0)