5
5
6
6
import hid .write
7
7
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)
8
20
9
- class WriteTest (unittest .TestCase ):
10
21
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
+
12
29
13
- def target ():
14
- pass
30
+ def raise_exception ():
31
+ raise Exception ( 'Child exception' )
15
32
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 )
17
42
process .start ()
18
43
process .join ()
19
44
result = process .result ()
@@ -22,11 +47,8 @@ def target():
22
47
hid .write .ProcessResult (return_value = None , exception = None ), result )
23
48
24
49
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 )
30
52
process .start ()
31
53
# Get the result before the child process has completed.
32
54
self .assertIsNone (process .result ())
@@ -35,14 +57,11 @@ def target():
35
57
process .kill ()
36
58
37
59
def test_process_with_result_child_exception (self ):
38
-
39
- def target ():
40
- raise Exception ('Child exception' )
41
-
42
60
# Silence stderr while the child exception is being raised to avoid
43
61
# polluting the terminal output.
44
62
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 )
46
65
process .start ()
47
66
process .join ()
48
67
result = process .result ()
@@ -53,11 +72,7 @@ def target():
53
72
self .assertEqual ('Child exception' , str (result .exception ))
54
73
55
74
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 )
61
76
process .start ()
62
77
process .join ()
63
78
result = process .result ()
0 commit comments