@@ -59,6 +59,51 @@ tests =
59
59
, testProperty " 5" unit_catch_5
60
60
, testProperty " 6" unit_catch_6
61
61
]
62
+ , testGroup " masking state"
63
+ [ testProperty " set (IO)"
64
+ $ forall_masking_states unit_set_masking_state_IO
65
+ , testProperty " set (IOSim)"
66
+ $ forall_masking_states unit_set_masking_state_ST
67
+
68
+ , testProperty " unmask (IO)"
69
+ $ forall_masking_states $ \ ms ->
70
+ forall_masking_states $ \ ms' -> unit_unmask_IO ms ms'
71
+ , testProperty " unmask (IOSim)"
72
+ $ forall_masking_states $ \ ms ->
73
+ forall_masking_states $ \ ms' -> unit_unmask_ST ms ms'
74
+
75
+ , testProperty " fork (IO)"
76
+ $ forall_masking_states unit_fork_masking_state_IO
77
+ , testProperty " fork (IOSim)"
78
+ $ forall_masking_states unit_fork_masking_state_ST
79
+
80
+ , testProperty " fork unmask (IO)"
81
+ $ forall_masking_states $ \ ms ->
82
+ forall_masking_states $ \ ms' -> unit_fork_unmask_IO ms ms'
83
+ , testProperty " fork unmask (IOSim)"
84
+ $ forall_masking_states $ \ ms ->
85
+ forall_masking_states $ \ ms' -> unit_fork_unmask_ST ms ms'
86
+
87
+ , testProperty " catch (IO)"
88
+ $ forall_masking_states unit_catch_throwIO_masking_state_IO
89
+ , testProperty " catch (IOSim)"
90
+ $ forall_masking_states unit_catch_throwIO_masking_state_ST
91
+
92
+ , testProperty " catch: throwTo (IO)"
93
+ $ forall_masking_states unit_catch_throwTo_masking_state_IO
94
+ , testProperty " catch: throwTo (IOSim)"
95
+ $ forall_masking_states unit_catch_throwTo_masking_state_ST
96
+
97
+ , testProperty " catch: throwTo async (IO)"
98
+ $ forall_masking_states unit_catch_throwTo_masking_state_async_IO
99
+ , testProperty " catch: throwTo async (IOSim)"
100
+ $ forall_masking_states unit_catch_throwTo_masking_state_async_ST
101
+
102
+ , testProperty " catch: throwTo async blocking (IO)"
103
+ $ forall_masking_states unit_catch_throwTo_masking_state_async_mayblock_IO
104
+ , testProperty " catch: throwTo async blocking (IOSim)"
105
+ $ forall_masking_states unit_catch_throwTo_masking_state_async_mayblock_ST
106
+ ]
62
107
, testProperty " evaluate unit test" unit_evaluate_0
63
108
, testGroup " forkIO unit tests"
64
109
[ testProperty " 1" unit_fork_1
@@ -887,6 +932,253 @@ prop_timeout_no_deadlock_Sim = runSimOrThrow prop_timeout_no_deadlockM
887
932
prop_timeout_no_deadlock_IO :: Property
888
933
prop_timeout_no_deadlock_IO = ioProperty prop_timeout_no_deadlockM
889
934
935
+
936
+ --
937
+ -- MonadMask properties
938
+ --
939
+
940
+ setMaskingState_ :: MonadMask m => MaskingState -> m a -> m a
941
+ setMaskingState_ Unmasked = id
942
+ setMaskingState_ MaskedInterruptible = mask_
943
+ setMaskingState_ MaskedUninterruptible = uninterruptibleMask_
944
+
945
+ setMaskingState :: MonadMask m => MaskingState
946
+ -> ((forall x . m x -> m x ) -> m a ) -> m a
947
+ setMaskingState Unmasked = \ f -> f id
948
+ setMaskingState MaskedInterruptible = mask
949
+ setMaskingState MaskedUninterruptible = uninterruptibleMask
950
+
951
+ maxMS :: MaskingState -> MaskingState -> MaskingState
952
+ maxMS MaskedUninterruptible _ = MaskedUninterruptible
953
+ maxMS _ MaskedUninterruptible = MaskedUninterruptible
954
+ maxMS MaskedInterruptible _ = MaskedInterruptible
955
+ maxMS _ MaskedInterruptible = MaskedInterruptible
956
+ maxMS Unmasked Unmasked = Unmasked
957
+
958
+
959
+ forall_masking_states :: (MaskingState -> Property )
960
+ -> Property
961
+ forall_masking_states prop =
962
+ -- make sure that the property is executed once!
963
+ withMaxSuccess 1 $
964
+ foldr (\ ms p -> counterexample (show ms) (prop ms) .&&. p)
965
+ (property True )
966
+ [Unmasked , MaskedInterruptible , MaskedUninterruptible ]
967
+
968
+ -- | Check that setting masking state is effective.
969
+ --
970
+ prop_set_masking_state :: MonadMaskingState m
971
+ => MaskingState
972
+ -> m Property
973
+ prop_set_masking_state ms =
974
+ setMaskingState_ ms $ do
975
+ ms' <- getMaskingState
976
+ return (ms === ms')
977
+
978
+ unit_set_masking_state_IO :: MaskingState -> Property
979
+ unit_set_masking_state_IO =
980
+ ioProperty . prop_set_masking_state
981
+
982
+ unit_set_masking_state_ST :: MaskingState -> Property
983
+ unit_set_masking_state_ST ms =
984
+ runSimOrThrow (prop_set_masking_state ms)
985
+
986
+
987
+ -- | Check that 'unmask' restores the masking state.
988
+ --
989
+ prop_unmask :: MonadMaskingState m
990
+ => MaskingState
991
+ -> MaskingState
992
+ -> m Property
993
+ prop_unmask ms ms' =
994
+ setMaskingState_ ms $
995
+ setMaskingState ms' $ \ unmask -> do
996
+ ms'' <- unmask getMaskingState
997
+ return (ms'' === ms)
998
+
999
+ unit_unmask_IO :: MaskingState -> MaskingState -> Property
1000
+ unit_unmask_IO ms ms' = ioProperty $ prop_unmask ms ms'
1001
+
1002
+ unit_unmask_ST :: MaskingState -> MaskingState -> Property
1003
+ unit_unmask_ST ms ms' = runSimOrThrow $ prop_unmask ms ms'
1004
+
1005
+
1006
+ -- | Check that masking state is inherited by a forked thread.
1007
+ --
1008
+ prop_fork_masking_state :: ( MonadMaskingState m
1009
+ , MonadFork m
1010
+ , MonadSTM m
1011
+ )
1012
+ => MaskingState -> m Property
1013
+ prop_fork_masking_state ms = setMaskingState_ ms $ do
1014
+ var <- newEmptyTMVarIO
1015
+ _ <- forkIO $ getMaskingState >>= atomically . putTMVar var
1016
+ ms' <- atomically $ takeTMVar var
1017
+ return $ ms === ms'
1018
+
1019
+ unit_fork_masking_state_IO :: MaskingState -> Property
1020
+ unit_fork_masking_state_IO =
1021
+ ioProperty . prop_fork_masking_state
1022
+
1023
+ unit_fork_masking_state_ST :: MaskingState -> Property
1024
+ unit_fork_masking_state_ST ms =
1025
+ runSimOrThrow (prop_fork_masking_state ms)
1026
+
1027
+
1028
+ -- | Check that 'unmask' restores the masking state in a forked thread.
1029
+ --
1030
+ -- Note: unlike 'prop_unmask', 'forkIOWithUnmask's 'unmask' function will
1031
+ -- restore 'Unmasked' state, not the encosing masking state.
1032
+ --
1033
+ prop_fork_unmask :: ( MonadMaskingState m
1034
+ , MonadFork m
1035
+ , MonadSTM m
1036
+ )
1037
+ => MaskingState
1038
+ -> MaskingState
1039
+ -> m Property
1040
+ prop_fork_unmask ms ms' =
1041
+ setMaskingState_ ms $
1042
+ setMaskingState_ ms' $ do
1043
+ var <- newEmptyTMVarIO
1044
+ _ <- forkIOWithUnmask $ \ unmask -> unmask getMaskingState
1045
+ >>= atomically . putTMVar var
1046
+ ms'' <- atomically $ takeTMVar var
1047
+ return $ Unmasked === ms''
1048
+
1049
+ unit_fork_unmask_IO :: MaskingState -> MaskingState -> Property
1050
+ unit_fork_unmask_IO ms ms' = ioProperty $ prop_fork_unmask ms ms'
1051
+
1052
+ unit_fork_unmask_ST :: MaskingState -> MaskingState -> Property
1053
+ unit_fork_unmask_ST ms ms' = runSimOrThrow $ prop_fork_unmask ms ms'
1054
+
1055
+
1056
+ -- | A unit test which checks the masking state in the context of a catch
1057
+ -- handler.
1058
+ --
1059
+ prop_catch_throwIO_masking_state :: forall m . MonadMaskingState m
1060
+ => MaskingState -> m Property
1061
+ prop_catch_throwIO_masking_state ms =
1062
+ setMaskingState_ ms $ do
1063
+ throwIO (userError " error" )
1064
+ `catch` \ (_ :: IOError ) -> do
1065
+ ms' <- getMaskingState
1066
+ return $ ms' === MaskedInterruptible `maxMS` ms
1067
+
1068
+ unit_catch_throwIO_masking_state_IO :: MaskingState -> Property
1069
+ unit_catch_throwIO_masking_state_IO ms =
1070
+ ioProperty $ prop_catch_throwIO_masking_state ms
1071
+
1072
+ unit_catch_throwIO_masking_state_ST :: MaskingState -> Property
1073
+ unit_catch_throwIO_masking_state_ST ms =
1074
+ runSimOrThrow (prop_catch_throwIO_masking_state ms)
1075
+
1076
+
1077
+ -- | Like 'prop_catch_masking_state' but using 'throwTo'.
1078
+ --
1079
+ prop_catch_throwTo_masking_state :: forall m .
1080
+ ( MonadMaskingState m
1081
+ , MonadFork m
1082
+ )
1083
+ => MaskingState -> m Property
1084
+ prop_catch_throwTo_masking_state ms =
1085
+ setMaskingState_ ms $ do
1086
+ tid <- myThreadId
1087
+ (throwTo tid (userError " error" ) >> error " impossible" )
1088
+ `catch` \ (_ :: IOError ) -> do
1089
+ ms' <- getMaskingState
1090
+ return $ ms' === MaskedInterruptible `maxMS` ms
1091
+
1092
+ unit_catch_throwTo_masking_state_IO :: MaskingState -> Property
1093
+ unit_catch_throwTo_masking_state_IO =
1094
+ ioProperty . prop_catch_throwTo_masking_state
1095
+
1096
+ unit_catch_throwTo_masking_state_ST :: MaskingState -> Property
1097
+ unit_catch_throwTo_masking_state_ST ms =
1098
+ runSimOrThrow $ prop_catch_throwTo_masking_state ms
1099
+
1100
+
1101
+ -- | Like 'prop_catch_throwTo_masking_state' but using 'throwTo' to a different
1102
+ -- thread which is in a non-blocking mode.
1103
+ --
1104
+ prop_catch_throwTo_masking_state_async :: forall m .
1105
+ ( MonadMaskingState m
1106
+ , MonadFork m
1107
+ , MonadSTM m
1108
+ , MonadDelay m
1109
+ )
1110
+ => MaskingState -> m Property
1111
+ prop_catch_throwTo_masking_state_async ms = do
1112
+ sgnl <- newEmptyTMVarIO
1113
+ var <- newEmptyTMVarIO
1114
+ tid <- forkIO $
1115
+ setMaskingState ms $ \ unmask ->
1116
+ (do atomically $ putTMVar sgnl ()
1117
+ unmask (threadDelay 1 )
1118
+ )
1119
+ `catch` \ (_ :: IOError ) -> do
1120
+ ms' <- getMaskingState
1121
+ atomically $ putTMVar var (ms' === ms `maxMS` MaskedInterruptible )
1122
+ -- wait until the catch handler is installed
1123
+ atomically $ takeTMVar sgnl
1124
+ -- the forked thread is interruptibly blocked on `threadDelay`,
1125
+ -- `throwTo` will not block
1126
+ throwTo tid (userError " error" )
1127
+ atomically $ takeTMVar var
1128
+
1129
+
1130
+ unit_catch_throwTo_masking_state_async_IO :: MaskingState -> Property
1131
+ unit_catch_throwTo_masking_state_async_IO =
1132
+ ioProperty . prop_catch_throwTo_masking_state_async
1133
+
1134
+ unit_catch_throwTo_masking_state_async_ST :: MaskingState -> Property
1135
+ unit_catch_throwTo_masking_state_async_ST ms =
1136
+ runSimOrThrow (prop_catch_throwTo_masking_state_async ms)
1137
+
1138
+
1139
+ -- | Like 'prop_catch_throwTo_masking_state_async' but 'throwTo' will block if
1140
+ -- masking state is set to 'MaskedUninterruptible'. This makes sure that the
1141
+ -- 'willBlock' branch of 'ThrowTo' in 'schedule' is covered.
1142
+ --
1143
+ prop_catch_throwTo_masking_state_async_mayblock :: forall m .
1144
+ ( MonadMaskingState m
1145
+ , MonadFork m
1146
+ , MonadSTM m
1147
+ , MonadDelay m
1148
+ )
1149
+ => MaskingState -> m Property
1150
+ prop_catch_throwTo_masking_state_async_mayblock ms = do
1151
+ sgnl <- newEmptyTMVarIO
1152
+ var <- newEmptyTMVarIO
1153
+ tid <- forkIO $
1154
+ setMaskingState ms $ \ unmask ->
1155
+ (do atomically $ putTMVar sgnl ()
1156
+ -- if 'ms' is 'MaskedUninterruptible' then the following
1157
+ -- 'threadDelay' will block.
1158
+ threadDelay 0.1
1159
+ -- make sure that even in 'MaskedUninterruptible' the thread
1160
+ -- unblocks so async exceptions can be delivered.
1161
+ unmask (threadDelay 1 )
1162
+ )
1163
+ `catch` \ (_ :: IOError ) -> do
1164
+ ms' <- getMaskingState
1165
+ atomically $ putTMVar var (ms' === ms `maxMS` MaskedInterruptible )
1166
+ -- wait until the catch handler is installed
1167
+ atomically $ takeTMVar sgnl
1168
+ threadDelay 0.05
1169
+ -- we know the forked thread is interruptibly blocked on `threadDelay`,
1170
+ -- `throwTo` will not be blocked.
1171
+ throwTo tid (userError " error" )
1172
+ atomically $ takeTMVar var
1173
+
1174
+ unit_catch_throwTo_masking_state_async_mayblock_IO :: MaskingState -> Property
1175
+ unit_catch_throwTo_masking_state_async_mayblock_IO =
1176
+ ioProperty . prop_catch_throwTo_masking_state_async_mayblock
1177
+
1178
+ unit_catch_throwTo_masking_state_async_mayblock_ST :: MaskingState -> Property
1179
+ unit_catch_throwTo_masking_state_async_mayblock_ST ms =
1180
+ runSimOrThrow (prop_catch_throwTo_masking_state_async_mayblock ms)
1181
+
890
1182
--
891
1183
-- Utils
892
1184
--
0 commit comments