on master help sign in
refactor the input layer for testing. build first test that actually simulate typing
commit849fc716a59879969d0e2e00392cd115061fee5c authorCorey O'Connor <corey@ooyala.com> dateSun 10 Nov 2013, 20:38 (13 years ago) parent89ffd68
3 files changed+161−95
src/Graphics/Vty/Input.hs+8−65 src/Graphics/Vty/Input/Internal.hs+61−1 test/VerifyUsingMockInput.hs+92−29
src/Graphics/Vty/Input.hs+8−65
@@ -32,38 +32,21 @@ import Graphics.Vty.Input.Data
3232import Graphics.Vty.Input.Internal
3333import Graphics.Vty.Input.Terminfo
3434
35−import Data.Char
36−import Data.IORef
37−import Data.Word
38−
39−import Control.Monad (when, void)
4035import Control.Concurrent
41−import Control.Exception
42−
43−import qualified GHC.Event as GHC
4436
4537import System.Console.Terminfo
4638
4739import System.Posix.Signals.Exts
4840import System.Posix.Terminal
49−import System.Posix.IO ( stdInput
50− , fdReadBuf
51− , setFdOption
52− , FdOption(..)
53− )
54−
55−import Foreign ( alloca, poke, peek, Ptr )
41+import System.Posix.IO (stdInput)
5642
5743defaultEscDelay :: Int
58−defaultEscDelay = 0
44+defaultEscDelay = 4000
5945
6046-- | Set up the terminal for input. Returns a function which reads key
6147-- events, and a function for shutting down the terminal access.
6248initTermInput :: Int -> Terminal -> IO (IO Event, IO ())
6349initTermInput escDelay terminal = do
64− eventChannel <- newChan
65− inputChannel <- newChan
66− hadInput <- newEmptyMVar
6750 attr <- getTerminalAttributes stdInput
6851 -- disable IXON,
6952 let attr' = foldl withoutMode attr [ StartStopOutput, KeyboardInterrupts
@@ -71,59 +54,19 @@ initTermInput escDelay terminal = do
7154 ]
7255 setTerminalAttributes stdInput attr' Immediately
7356 set_term_timing
74− let finishAtomicInput = writeChan inputChannel '\xFFFE'
75− inputThread :: IO ()
76− inputThread = do
77− _ <- alloca $ \(input_buffer :: Ptr Word8) -> do
78− let loop = do
79− setFdOption stdInput NonBlockingRead False
80− threadWaitRead stdInput
81− setFdOption stdInput NonBlockingRead True
82− _ <- try readAll :: IO (Either IOException ())
83− when (escDelay == 0) finishAtomicInput
84− loop
85− readAll = do
86− poke input_buffer 0
87− bytes_read <- fdReadBuf stdInput input_buffer 1
88− input_char <- fmap (chr . fromIntegral) $ peek input_buffer
89− when (bytes_read > 0) $ do
90− _ <- tryPutMVar hadInput () -- signal input
91− writeChan inputChannel input_char
92− readAll
93− loop
94− return ()
95− -- | If there is no input for some time, this thread puts '\xFFFE' in the
96− -- inputChannel.
97− noInputThread :: IO ()
98− noInputThread = when (escDelay > 0) loop
99− where loop = do
100− takeMVar hadInput -- wait for some input
101− threadDelay escDelay -- microseconds
102− hadNoInput <- isEmptyMVar hadInput -- no input yet?
103− -- TODO(corey): there is a race between here and the inputThread.
104− when hadNoInput finishAtomicInput
105− loop
106−
107− caps_legacy_table = map_to_legacy_table $ caps_classify_table terminal keys_from_caps_table
108− term_event_classify_table = concat $ caps_legacy_table : ansi_classify_table
109− term_event_classifier = classify term_event_classify_table
110−
111− eventThreadId <- forkIO $ void $ inputToEventThread term_event_classifier inputChannel eventChannel
112− inputThreadId <- forkIO $ inputThread
113− noInputThreadId <- forkIO $ noInputThread
57+ let caps_legacy_table = map_to_legacy_table $ caps_classify_table terminal keys_from_caps_table
58+ classify_table = concat $ caps_legacy_table : ansi_classify_table
59+ (eventChannel, shutdown_input) <- initInputForFd escDelay classify_table stdInput
11460 let pokeIO = Catch $ do
11561 let e = error "(getsize in input layer)"
11662 setTerminalAttributes stdInput attr' Immediately
11763 writeChan eventChannel (EvResize e e)
11864 _ <- installHandler windowChange pokeIO Nothing
11965 _ <- installHandler continueProcess pokeIO Nothing
120− -- TODO(corey): killThread is a bit risky for my tastes.
121− let uninit = do
122− killThread eventThreadId
123− killThread inputThreadId
124− killThread noInputThreadId
66+ let shutdown_input' = do
67+ shutdown_input
12568 _ <- installHandler windowChange Ignore Nothing
12669 _ <- installHandler continueProcess Ignore Nothing
12770 setTerminalAttributes stdInput attr Immediately
128− return (readChan eventChannel, uninit)
71+ return (readChan eventChannel, shutdown_input')
12972
src/Graphics/Vty/Input/Internal.hs+61−1
@@ -4,6 +4,8 @@ import Graphics.Vty.Input.Data
44
55import Codec.Binary.UTF8.Generic (decode)
66import Control.Concurrent
7+import Control.Exception (try, IOException(..))
8+import Control.Monad (when, void)
79
810import Data.Char
911import Data.List( inits )
@@ -12,6 +14,14 @@ import Data.Maybe ( mapMaybe )
1214import qualified Data.Set as S( fromList, member )
1315import Data.Word
1416
17+import Foreign ( alloca, poke, peek, Ptr )
18+
19+import System.Posix.IO ( fdReadBuf
20+ , setFdOption
21+ , FdOption(..)
22+ )
23+import System.Posix.Types (Fd(..))
24+
1525data KClass
1626 = Valid Key [Modifier]
1727 | Invalid
@@ -22,7 +32,7 @@ data KClass
2232
2333inputToEventThread :: (String -> KClass) -> Chan Char -> Chan Event -> IO ()
2434inputToEventThread classifier inputChannel eventChannel = loop []
25− where loop kb = case (classifier kb) of
35+ where loop kb = case classifier kb of
2636 Prefix -> do
2737 c <- readChan inputChannel
2838 if c == '\xFFFD'
@@ -88,4 +98,54 @@ utf8Length c
8898 | c < 0xF0 = 3
8999 | otherwise = 4
90100
101+initInputForFd :: Int -> ClassifyTable -> Fd -> IO (Chan Event, IO ())
102+initInputForFd escDelay classify_table input_fd = do
103+ inputChannel <- newChan
104+ eventChannel <- newChan
105+ hadInput <- newEmptyMVar
106+ let finishAtomicInput = writeChan inputChannel '\xFFFE'
107+ inputThread :: IO ()
108+ inputThread = do
109+ _ <- alloca $ \(input_buffer :: Ptr Word8) -> do
110+ let loop = do
111+ setFdOption input_fd NonBlockingRead False
112+ threadWaitRead input_fd
113+ setFdOption input_fd NonBlockingRead True
114+ _ <- try readAll :: IO (Either IOException ())
115+ when (escDelay == 0) finishAtomicInput
116+ loop
117+ readAll = do
118+ poke input_buffer 0
119+ bytes_read <- fdReadBuf input_fd input_buffer 1
120+ input_char <- fmap (chr . fromIntegral) $ peek input_buffer
121+ when (bytes_read > 0) $ do
122+ _ <- tryPutMVar hadInput () -- signal input
123+ writeChan inputChannel input_char
124+ readAll
125+ loop
126+ return ()
127+ -- | If there is no input for some time, this thread puts '\xFFFE' in the
128+ -- inputChannel.
129+ noInputThread :: IO ()
130+ noInputThread = when (escDelay > 0) loop
131+ where loop = do
132+ takeMVar hadInput -- wait for some input
133+ threadDelay escDelay -- microseconds
134+ hadNoInput <- isEmptyMVar hadInput -- no input yet?
135+ -- TODO(corey): there is a race between here and the inputThread.
136+ when hadNoInput finishAtomicInput
137+ loop
138+ classifier = classify classify_table
139+ eventThreadId <- forkIO $ void $ inputToEventThread classifier inputChannel eventChannel
140+ inputThreadId <- forkIO $ inputThread
141+ noInputThreadId <- forkIO $ noInputThread
142+ -- TODO(corey): killThread is a bit risky for my tastes.
143+ -- H - somewhat mitigated by sending a magic terminate character?
144+ let shutdown_input = do
145+ writeChan inputChannel '\xFFFD'
146+ killThread noInputThreadId
147+ killThread eventThreadId
148+ killThread inputThreadId
149+ return (eventChannel, shutdown_input)
150+
91151foreign import ccall "vty_set_term_timing" set_term_timing :: IO ()
test/VerifyUsingMockInput.hs+92−29
@@ -11,7 +11,6 @@ import Verify.Graphics.Vty.Terminal
1111import Data.List (intersperse, permutations)
1212
1313import Graphics.Vty hiding (resize)
14−import Graphics.Vty.Input
1514import Graphics.Vty.Input.Data
1615import Graphics.Vty.Input.Internal
1716import Graphics.Vty.Input.Terminfo
@@ -21,8 +20,12 @@ import Control.Concurrent
2120import Control.Exception
2221import Control.Monad
2322
23+import Data.IORef
24+
2425import System.Console.Terminfo
25−import System.IO
26+import System.Posix.IO
27+import System.Posix.Types
28+import System.Timeout
2629
2730import Test.Framework.Providers.SmallCheck
2831import Test.Framework
@@ -38,48 +41,95 @@ forEachOf l = over (generate (\n -> take n l))
3841
3942data InputEvent
4043 = Bytes String -- | input sequence encoded as a string. Regardless, the input is read a byte at a time.
41− | Delay Int -- | millisecond delay
44+ | Delay Int -- | microsecond delay
4245 deriving Show
4346
4447type InputSpec = [InputEvent]
4548
4649type ExpectedSpec = [Event]
4750
48−exec_input_spec :: InputSpec -> Handle -> IO ()
49−exec_input_spec input out_handle = forM_ input f
51+synthesize_input :: InputSpec -> Fd -> IO ()
52+synthesize_input input out_handle = forM_ input f >> (void $ fdWrite out_handle "\xFFFD")
5053 where
51− f (Bytes str) = hPutStr out_handle str >> hFlush out_handle
52− f (Delay t) = threadDelay (t*1000)
54+ f (Bytes str) = void $ fdWrite out_handle str
55+ f (Delay t) = threadDelay t
56+
57+min_detectable_delay :: Int
58+min_detectable_delay = 1000
59+
60+min_timout :: Int
61+min_timout = 4000000
62+
63+test_key_delay :: Int
64+test_key_delay = min_detectable_delay * 4
65+
66+test_esc_sample_delay :: Int
67+test_esc_sample_delay = min_detectable_delay * 2
68+
69+gen_events_using_io_actions :: Int -> IO () -> IO () -> IO ()
70+gen_events_using_io_actions max_duration input_action output_action = do
71+ let max_duration' = max min_timout max_duration
72+ read_complete <- newEmptyMVar
73+ write_complete <- newEmptyMVar
74+ _ <- forkIO $ input_action `finally` putMVar write_complete ()
75+ _ <- forkIO $ output_action `finally` putMVar read_complete ()
76+ Just () <- timeout max_duration' $ takeMVar write_complete
77+ Just () <- timeout max_duration' $ takeMVar read_complete
78+ return ()
79+
80+assert_events_from_syn_input :: ClassifyTable -> InputSpec -> ExpectedSpec -> IO Bool
81+assert_events_from_syn_input table input_spec expected_events = do
82+ let max_duration = sum [t | Delay t <- input_spec] + min_detectable_delay
83+ event_count = length expected_events
84+ -- create pipe
85+ (output_fd, input_fd) <- createPipe
86+ (output, shutdown_input) <- initInputForFd test_esc_sample_delay table output_fd
87+ events_ref <- newIORef []
88+ let write_wait_close = do
89+ synthesize_input input_spec input_fd
90+ threadDelay min_detectable_delay
91+ shutdown_input
92+ threadDelay min_detectable_delay
93+ closeFd input_fd
94+ closeFd output_fd
95+ -- drain output pipe
96+ let read_all_events = read_loop event_count
97+ read_loop 0 = return ()
98+ read_loop n = do
99+ e <- readChan output
100+ modifyIORef events_ref ((:) e)
101+ read_loop (n - 1)
102+ gen_events_using_io_actions max_duration write_wait_close read_all_events
103+ out_events <- reverse <$> readIORef events_ref
104+ print (input_spec, expected_events, out_events)
105+ return $ out_events == expected_events
53106
54107assert_events_from_input_block :: ClassifyTable -> InputSpec -> ExpectedSpec -> IO Bool
55108assert_events_from_input_block table input_spec expected_events = do
56− print input_spec
57109 let classifier = classify table
110+ max_duration = sum [t | Delay t <- input_spec] + min_detectable_delay
58111 input <- newChan
59112 output <- newChan
60− read_complete <- newEmptyMVar
61− write_complete <- newEmptyMVar
62− _ <- forkIO $ write_input_spec_to_chan input input_spec
63− `finally` putMVar write_complete ()
64− _ <- forkIO $ inputToEventThread classifier input output
65− `finally` putMVar read_complete ()
66− () <- takeMVar write_complete
67− () <- takeMVar read_complete
68− -- TODO: use STM channel
69− -- assures the next line does not block.
70− writeList2Chan output $ replicate (length expected_events) undefined
71− out_events <- take (length expected_events) <$> getChanContents output
113+ gen_events_using_io_actions
114+ max_duration
115+ (write_input_spec_to_chan input_spec input)
116+ (inputToEventThread classifier input output)
117+ -- TODO: use STM TChan?
118+ -- assures reading "length expected_events" from the channel does not block.
119+ let min_event_count = length expected_events
120+ writeList2Chan output $ replicate min_event_count undefined
121+ out_events <- take min_event_count <$> getChanContents output
72122 return $ out_events == expected_events
73123
74−write_input_spec_to_chan :: Chan Char -> InputSpec -> IO ()
75−write_input_spec_to_chan chan [] = do
124+write_input_spec_to_chan :: InputSpec -> Chan Char -> IO ()
125+write_input_spec_to_chan [] chan = do
76126 writeChan chan '\xFFFD'
77−write_input_spec_to_chan chan (Bytes str : input_spec') = do
127+write_input_spec_to_chan (Bytes str : input_spec') chan = do
78128 writeList2Chan chan str
79− write_input_spec_to_chan chan input_spec'
80−write_input_spec_to_chan chan (Delay _t : input_spec') = do
129+ write_input_spec_to_chan input_spec' chan
130+write_input_spec_to_chan (Delay _t : input_spec') chan = do
81131 writeChan chan '\xFFFE'
82− write_input_spec_to_chan chan input_spec'
132+ write_input_spec_to_chan input_spec' chan
83133
84134newtype InputBlocksUsingTable event
85135 = InputBlocksUsingTable ([(String,event)] -> [(String, event)])
@@ -107,14 +157,27 @@ verify_keys_from_caps_table_block_to_event = forAll $ \(InputBlocksUsingTable ge
107157 input_seq = gen table
108158 events = [e | (_,e) <- input_seq]
109159 keydowns = [Bytes s | (s,_) <- input_seq]
110− input = intersperse (Delay defaultEscDelay) keydowns
160+ input = intersperse (Delay test_key_delay) keydowns
111161 assert_events_from_input_block (map_to_legacy_table table) input events
112162
163+verify_simple_syn_input_to_event :: Property IO
164+verify_simple_syn_input_to_event = forAll $ \(InputBlocksUsingTable gen) ->
165+ forEachOf terminals_of_interest $ \term_name -> monadic $ do
166+ term <- setupTerm term_name
167+ let table = caps_classify_table term keys_from_caps_table
168+ input_seq = gen table
169+ events = [e | (_,e) <- input_seq]
170+ keydowns = [Bytes s | (s,_) <- input_seq]
171+ input = intersperse (Delay test_key_delay) keydowns
172+ assert_events_from_syn_input (map_to_legacy_table table) input events
173+
113174main :: IO ()
114175main = defaultMain
115176 [ testProperty "basic block generated from a single ansi chars to event translation"
116− verify_simple_input_block_to_event
177+ verify_simple_input_block_to_event
117178 , testProperty "key sequences read from caps table map to expected events"
118− verify_keys_from_caps_table_block_to_event
179+ verify_keys_from_caps_table_block_to_event
180+ , testProperty "synthesized typing from basic blocks translates to expected events"
181+ verify_simple_syn_input_to_event
119182 ]
120183