1module Main (main) where
3import Control.Exception (SomeException(..))
4import Data.Word (Word8, Word32, Word64)
5import Streamly.Test.Common (listEquals, checkListEqual, chooseInt, equals)
6import Test.Hspec (Spec, hspec, describe)
7import Test.Hspec.QuickCheck
9 (arbitrary, forAll, elements, Property,
10 property, listOf, vectorOf, (.&&.), Gen)
11import Test.QuickCheck.Monadic (monadicIO, assert, run)
13import qualified Data.List as List
14import qualified Prelude
15import qualified Streamly.Internal.Data.Array.Foreign as A
16import qualified Streamly.Internal.Data.Fold as FL
17import qualified Streamly.Internal.Data.Parser.ParserD as P
18import qualified Streamly.Internal.Data.Producer.Source as Source
19import qualified Streamly.Internal.Data.Producer as Producer
20import qualified Streamly.Internal.Data.Stream.IsStream as S
21import qualified Streamly.Internal.Data.Stream.StreamD as D
22import qualified Streamly.Internal.Data.Unfold as Unfold
23import qualified Test.Hspec as H
25import Prelude hiding (sequence)
27#if MIN_VERSION_QuickCheck(2,14,0)
29import Test.QuickCheck (chooseAny)
33import System.Random (Random(random))
34import Test.QuickCheck.Gen (Gen(MkGen))
36-- | Generates a random element over the natural range of `a`.
37chooseAny :: Random a => Gen a
38chooseAny = MkGen (\r _ -> let (x,_) = random r in x)
61 forAll (listOf $ chooseInt (min_value, max_value))
63 case (==) <$> (S.parseD (P.fromFold FL.sum) (S.fromList ls))
64 <*> (S.fold FL.sum (S.fromList ls)) of
65 Right is_equal -> is_equal
70 forAll (chooseInt (min_value, max_value)) $ \x ->
71 case S.parseD (P.fromPure x) (S.fromList [1 :: Int]) of
77 forAll (chooseInt (min_value, max_value)) $ \x ->
78 case S.parseD (P.fromEffect $ return x) (S.fromList [1 :: Int]) of
85 case S.parseD (P.die "die test") (S.fromList [0 :: Int]) of
92 case S.parseD (P.dieM (Right "die test")) (S.fromList [0 :: Int]) of
96-- Element Parser Tests
100 forAll (chooseInt (1, max_length)) $ \list_length ->
101 forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
102 case S.parseD P.peek (S.fromList ls) of
103 Right head_value -> case ls of
104 head_ls : _ -> head_value == head_ls
110 property (case S.parseD P.peek (S.fromList []) of
116 property (case S.parseD P.eof (S.fromList []) of
122 forAll (chooseInt (1, max_length)) $ \list_length ->
123 forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
124 case S.parseD P.eof (S.fromList ls) of
128satisfyPass :: Property
130 forAll (chooseInt (mid_value, max_value)) $ \first_element ->
131 forAll (listOf (chooseInt (min_value, max_value))) $ \ls_tail ->
133 ls = first_element : ls_tail
134 predicate = (>= mid_value)
136 case S.parseD (P.satisfy predicate) (S.fromList ls) of
137 Right r -> r == first_element
142 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
143 case S.parseD (P.satisfy predicate) (S.fromList ls) of
144 Right r -> case ls of
146 (x : _) -> predicate x && (r == x)
149 (x : _) -> not $ predicate x
151 predicate = (>= mid_value)
153-- Sequence Parsers Tests
154takeBetweenPass :: Property
156 forAll (chooseInt (min_value, max_value)) $ \m ->
157 forAll (chooseInt (m, max_value)) $ \n ->
158 forAll (chooseInt (m, max_value)) $ \list_length ->
159 forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
160 case S.parseD (P.takeBetween m n FL.toList) (S.fromList ls) of
162 let lpl = Prelude.length parsed_list
163 in checkListEqual parsed_list (Prelude.take lpl ls)
164 Left _ -> property False
167takeBetween :: Property
169 forAll (chooseInt (min_value, max_value)) $ \m ->
170 forAll (chooseInt (min_value, max_value)) $ \n ->
171 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
173 list_length = Prelude.length ls
175 case S.parseD (P.takeBetween m n FL.toList) (S.fromList ls) of
177 if m <= list_length && n >= m
179 let lpl = Prelude.length parsed_list
180 in checkListEqual parsed_list (Prelude.take
183 Left _ -> property (m > n || list_length < m)
187 forAll (chooseInt (min_value, max_value)) $ \n ->
188 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
189 case S.parseD (P.fromFold $ FL.take n FL.toList) (S.fromList ls) of
190 Right parsed_list -> checkListEqual parsed_list (Prelude.take n ls)
191 Left _ -> property False
193takeEQPass :: Property
195 forAll (chooseInt (min_value, max_value)) $ \n ->
196 forAll (chooseInt (n, max_value)) $ \list_length ->
197 forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
198 case S.parseD (P.takeEQ n FL.toList) (S.fromList ls) of
199 Right parsed_list -> checkListEqual parsed_list (Prelude.take n ls)
200 Left _ -> property False
204 forAll (chooseInt (min_value, max_value)) $ \n ->
205 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
207 list_length = Prelude.length ls
209 case S.parseD (P.takeEQ n FL.toList) (S.fromList ls) of
211 if (n <= list_length) then
212 checkListEqual parsed_list (Prelude.take n ls)
215 Left _ -> property (n > list_length)
217takeGEPass :: Property
219 forAll (chooseInt (min_value, max_value)) $ \n ->
220 forAll (chooseInt (n, max_value)) $ \list_length ->
221 forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
222 case S.parseD (P.takeGE n FL.toList) (S.fromList ls) of
223 Right parsed_list -> checkListEqual parsed_list ls
224 Left _ -> property False
228 forAll (chooseInt (min_value, max_value)) $ \n ->
229 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
231 list_length = Prelude.length ls
233 case S.parseD (P.takeGE n FL.toList) (S.fromList ls) of
235 if (n <= list_length) then
236 checkListEqual parsed_list ls
239 Left _ -> property (n > list_length)
243 -> FL.Fold (Either SomeException) Int [Int]
244 -> P.Parser (Either SomeException) Int [Int]
246 -> (Int -> [Int] -> [Int])
248nLessThanEqual0 tk ltk =
249 forAll (elements [0, (-1)]) $ \n ->
250 forAll (listOf arbitrary) $ \ls ->
251 case S.parseD (tk n FL.toList) (S.fromList ls) of
252 Right parsed_list -> checkListEqual parsed_list (ltk n ls)
253 Left _ -> property False
255takeProperties :: Spec
257 describe "take combinators when n <= 0/" $ do
258 prop "takeEQ n FL.toList = []" $
259 nLessThanEqual0 P.takeEQ (\_ -> const [])
260 prop "takeGE n FL.toList xs = xs" $
261 nLessThanEqual0 P.takeGE (\_ -> id)
264-- XXX lookAhead can't deal with EOF which in this case means when
265-- n==list_length, this test will fail. So excluding that case for now.
266lookAheadPass :: Property
268 forAll (chooseInt (min_value, max_value)) $ \n ->
270 takeWithoutConsume = P.lookAhead $ P.fromFold $ FL.take n FL.toList
272 parsed_list_1 <- takeWithoutConsume
273 parsed_list_2 <- takeWithoutConsume
274 return (parsed_list_1, parsed_list_2)
276 forAll (chooseInt (n+1, max_value)) $ \list_length ->
277 forAll (vectorOf list_length (chooseInt (min_value, max_value))) $ \ls ->
278 case S.parseD parseTwice (S.fromList ls) of
279 Right (ls_1, ls_2) -> checkListEqual ls_1 ls_2 .&&. checkListEqual ls_1 (Prelude.take n ls)
280 Left _ -> property $ False
284 forAll (chooseInt (min_value, max_value)) $ \n ->
286 takeWithoutConsume = P.lookAhead $ P.fromFold $ FL.take n FL.toList
288 parsed_list_1 <- takeWithoutConsume
289 parsed_list_2 <- takeWithoutConsume
290 return (parsed_list_1, parsed_list_2)
292 forAll (listOf (chooseInt (min_value, max_value))) $ \ls ->
293 case S.parseD parseTwice (S.fromList ls) of
294 Right (ls_1, ls_2) -> checkListEqual ls_1 ls_2 .&&. checkListEqual ls_1 (Prelude.take n ls)
295 Left _ -> property ((list_length < n) || (list_length == n && n == 0))
297 list_length = Prelude.length ls
301 forAll (listOf (chooseInt (0, 1))) $ \ ls ->
302 case S.parseD (P.takeWhile predicate FL.toList) (S.fromList ls) of
303 Right parsed_list -> checkListEqual parsed_list (Prelude.takeWhile predicate ls)
304 Left _ -> property False
308takeWhile1 :: Property
310 forAll (listOf (chooseInt (0, 1))) $ \ ls ->
311 case S.parseD (P.takeWhile1 predicate FL.toList) (S.fromList ls) of
312 Right parsed_list -> case ls of
316 checkListEqual parsed_list (Prelude.takeWhile predicate ls)
321 (x : _) -> property (not $ predicate x)
327 forAll (listOf (chooseInt (0, 1)))
329 case S.parseD parser (S.fromList ls) of
330 Right parsed -> checkListEqual parsed (groupByLF ls)
331 Left _ -> property False
336 parser = P.groupBy cmp FL.toList
339 | otherwise = head $ List.groupBy cmp lst
341groupByRolling :: Property
343 forAll (listOf (chooseInt (0, 1)))
345 case S.parseD parser (S.fromList ls) of
346 Right parsed -> checkListEqual parsed (groupByLF Nothing ls)
347 Left _ -> property False
352 parser = P.groupBy cmp FL.toList
354 groupByLF Nothing (x:xs) = x : groupByLF (Just x) xs
355 groupByLF (Just y) (x:xs) =
357 then x : groupByLF (Just x) xs
360takeEndByOrMax :: Property
362 forAll (chooseInt (min_value, max_value)) $ \n ->
363 forAll (listOf (chooseInt (0, 1))) $ \ls ->
364 case S.parseD (P.fromFold $ FL.takeEndBy_ predicate (FL.take n FL.toList)) (S.fromList ls) of
365 Right parsed_list -> checkListEqual parsed_list (Prelude.take n (Prelude.takeWhile (not . predicate) ls))
366 Left _ -> property False
372 forAll (listOf (elements [' ', 's']))
374 case S.parseD parser (S.fromList ls) of
375 Right parsed -> checkListEqual parsed (words' ls)
376 Left _ -> property False
381 parser = P.many (P.wordBy predicate FL.toList) FL.toList
384 in if wrds == [] && length lst > 0 then [""] else wrds
387serialWith :: Property
389 forAll (listOf (chooseInt (0, 1))) $ \ls ->
390 case S.parseD (P.serialWith (,) (P.satisfy (== 0)) (P.satisfy (== 1))) (S.fromList ls) of
391 Right (result_first, result_second) -> case ls of
392 0 : 1 : _ -> (result_first == 0) && (result_second == 1)
398splitWithFailLeft :: Property
400 property (case S.parseD (P.serialWith (,) (P.die "die") (P.fromPure (1 :: Int))) (S.fromList [1 :: Int]) of
404splitWithFailRight :: Property
406 property (case S.parseD (P.serialWith (,) (P.fromPure (1 :: Int)) (P.die "die")) (S.fromList [1 :: Int]) of
410splitWithFailBoth :: Property
412 property (case S.parseD (P.serialWith (,) (P.die "die") (P.die "die")) (S.fromList [1 :: Int]) of
416teeWithPass :: Property
418 forAll (chooseInt (min_value, max_value)) $ \n ->
419 forAll (listOf (chooseInt (0, 1))) $ \ls ->
421 prsr = P.fromFold $ FL.take n FL.toList
423 case S.parseD (P.teeWith (,) prsr prsr) (S.fromList ls) of
424 Right (ls_1, ls_2) -> checkListEqual (Prelude.take n ls) ls_1 .&&. checkListEqual ls_1 ls_2
425 Left _ -> property False
427teeWithFailLeft :: Property
429 property (case S.parseD (P.teeWith (,) (P.die "die") (P.fromPure (1 :: Int))) (S.fromList [1 :: Int]) of
433teeWithFailRight :: Property
435 property (case S.parseD (P.teeWith (,) (P.fromPure (1 :: Int)) (P.die "die")) (S.fromList [1 :: Int]) of
439teeWithFailBoth :: Property
441 property (case S.parseD (P.teeWith (,) (P.die "die") (P.die "die")) (S.fromList [1 :: Int]) of
445shortestPass :: Property
447 forAll (listOf (chooseInt(min_value, max_value))) $ \ls ->
449 half_mid_value = mid_value `Prelude.div` 2
450 prsr_1 = P.takeWhile (<= half_mid_value) FL.toList
451 prsr_2 = P.takeWhile (<= mid_value) FL.toList
452 prsr_shortest = P.shortest prsr_1 prsr_2
454 case S.parseD prsr_shortest (S.fromList ls) of
455 Right short_list -> checkListEqual short_list (Prelude.takeWhile (<= half_mid_value) ls)
456 Left _ -> property False
458shortestPassLeft :: Property
460 property (case S.parseD (P.shortest (P.die "die") (P.fromPure (1 :: Int))) (S.fromList [1 :: Int]) of
464shortestPassRight :: Property
466 property (case S.parseD (P.shortest (P.fromPure (1 :: Int)) (P.die "die")) (S.fromList [1 :: Int]) of
470shortestFailBoth :: Property
474 (P.shortest (P.die "die") (P.die "die"))
475 (S.fromList [1 :: Int]) of
479longestPass :: Property
481 forAll (listOf (chooseInt(min_value, max_value))) $ \ls ->
483 half_mid_value = mid_value `Prelude.div` 2
484 prsr_1 = P.takeWhile (<= half_mid_value) FL.toList
485 prsr_2 = P.takeWhile (<= mid_value) FL.toList
486 prsr_longest = P.longest prsr_1 prsr_2
488 case S.parseD prsr_longest (S.fromList ls) of
489 Right long_list -> long_list == Prelude.takeWhile (<= mid_value) ls
492longestPassLeft :: Property
494 property (case S.parseD (P.shortest (P.die "die") (P.fromPure (1 :: Int))) (S.fromList [1 :: Int]) of
498longestPassRight :: Property
500 property (case S.parseD (P.shortest (P.fromPure (1 :: Int)) (P.die "die")) (S.fromList [1 :: Int]) of
504longestFailBoth :: Property
507 (case S.parseD (P.shortest (P.die "die") (P.die "die")) (S.fromList [1 :: Int]) of
513 forAll (listOf (chooseInt (0, 1)))
515 let fldstp conL currL = return $ FL.Partial (conL ++ currL)
517 FL.Fold fldstp (return (FL.Partial [])) return
519 flip P.many concatFold
520 $ P.fromFold $ FL.takeEndBy_ (== 1) FL.toList
521 in case S.parseD prsr (S.fromList ls) of
523 checkListEqual res_list (Prelude.filter (== 0) ls)
524 Left _ -> property False
526many_empty :: Property
528 property (case S.parseD (flip P.many FL.toList (P.die "die")) (S.fromList [1 :: Int]) of
529 Right res_list -> checkListEqual res_list ([] :: [Int])
530 Left _ -> property False)
534 forAll (listOf (chooseInt (0, 1)))
536 let fldstp conL currL = return $ FL.Partial $ conL ++ currL
537 concatFold = FL.Fold fldstp (return (FL.Partial [])) return
539 flip P.some concatFold
540 $ P.fromFold $ FL.takeEndBy_ (== 1) FL.toList
541 in case S.parseD prsr (S.fromList ls) of
542 Right res_list -> res_list == Prelude.filter (== 0) ls
547 property (case S.parseD (P.some (P.die "die") FL.toList) (S.fromList [1 :: Int]) of
551-------------------------------------------------------------------------------
553-------------------------------------------------------------------------------
555applicative :: Property
557 forAll (listOf (chooseAny :: Gen Int)) $ \ list1 ->
558 forAll (listOf (chooseAny :: Gen Int)) $ \ list2 ->
561 <$> P.fromFold (FL.take (length list1) FL.toList)
562 <*> P.fromFold (FL.take (length list2) FL.toList)
565 run $ S.parseD parser (S.fromList $ list1 ++ list2)
566 listEquals (==) olist1 list1
567 listEquals (==) olist2 list2
571 forAll (vectorOf 11 (listOf (chooseAny :: Gen Int))) $ \ ins ->
572 let parsers = fmap (\xs -> P.fromFold $ FL.take (length xs) FL.toList) ins
576 (Prelude.sequence parsers)
577 (S.fromList $ concat ins)
578 listEquals (==) outs ins
582 forAll (listOf (chooseAny :: Gen Int)) $ \ list1 ->
583 forAll (listOf (chooseAny :: Gen Int)) $ \ list2 ->
585 olist1 <- P.fromFold (FL.take (length list1) FL.toList)
586 olist2 <- P.fromFold (FL.take (length list2) FL.toList)
587 return (olist1, olist2)
590 run $ S.parseD parser (S.fromList $ list1 ++ list2)
591 listEquals (==) olist1 list1
592 listEquals (==) olist2 list2
594-------------------------------------------------------------------------------
596-------------------------------------------------------------------------------
600 forAll (chooseInt (1,100)) $ \len ->
601 forAll (listOf (vectorOf len (chooseAny :: Gen Int))) $ \ ins ->
607 (P.fromFold $ FL.take len FL.toList) (S.fromList $ concat ins)
609 listEquals (==) outs ins
611-- basic sanity test for parsing from arrays
612parseUnfold :: Property
615 -- ls = input list (stream)
617 -- tlen = parser take size
620 <$> vectorOf len (chooseAny :: Gen Int)
621 <*> chooseInt (1, len)
622 <*> chooseInt (1, len)) $ \(ls, clen, tlen) ->
624 arrays <- S.toList $ S.arraysOf clen (S.fromList ls)
625 let src = Source.source (Just (Producer.OuterLoop arrays))
626 let parser = P.fromFold (FL.take tlen FL.toList)
629 $ Producer.concat Producer.fromList A.producer
631 Producer.simplify (Source.parseManyD parser readSrc)
634 $ S.unfoldMany Unfold.fromList
635 $ S.unfold streamParser src
637 listEquals (==) xs ls
639parserSequence :: Property
641 forAll (vectorOf 11 (listOf (chooseAny :: Gen Int))) $ \ins ->
643 let parsers = D.fromList
644 $ fmap (\xs -> P.fromFold $ FL.take (length xs) FL.sum) ins
645 let sequencedParser = P.sequence FL.sum parsers
648 S.parseD sequencedParser $ S.concatMap S.fromList (S.fromList ins)
649 equals (==) outs (sum $ map sum ins)
651-------------------------------------------------------------------------------
652-- Test for a particular case hit during fs events testing
653-------------------------------------------------------------------------------
656evId = [96,238,17,9,0,0,0,0]
659evFlags = [0,4,1,0,0,0,0,0]
662evPathLen = [71,0,0,0,0,0,0,0]
666 [47,85,115,101,114,115,47,118,111,108,47,118,101,109,98,97,47,99,111,109
667 ,112,111,115,101,119,101 ,108,108,45,116,101,99,104,47,69,110,103,47,112
668 ,114,111,106,101,99,116,115,47,115,116,114,101,97,109,108,121,47,115,116
669 ,114,101,97,109,108,121,47,116,109,112,47,122,122
673event = evId ++ evFlags ++ evPathLen ++ evPath
677 , eventFlags :: Word32
678 , eventAbsPath :: A.Array Word8
679 } deriving (Show, Ord, Eq)
681readOneEvent :: P.Parser IO Word8 Event
683 arr <- P.takeEQ 24 (A.writeN 24)
684 let arr1 = A.castUnsafe arr :: A.Array Word64
685 eid = A.unsafeIndex 0 arr1
686 eflags = A.unsafeIndex 1 arr1
687 pathLen = fromIntegral $ A.unsafeIndex 2 arr1
688 path <- P.takeEQ pathLen (A.writeN pathLen)
691 , eventFlags = fromIntegral eflags
692 , eventAbsPath = path
695parseMany2Events :: Property
701 $ S.parseManyD readOneEvent
702 $ S.fromList (concat (replicate 2 event))
704 assert (length xs == 2)
705 -- XXX assuming little endian machine
707 { eventId = 152170080
709 , eventAbsPath = A.fromList evPath
711 in listEquals (==) xs (replicate 2 ev)
713-------------------------------------------------------------------------------
715-------------------------------------------------------------------------------
718moduleName = "Data.Parser.ParserD"
724 modifyMaxSuccess (const maxTestCount) $ do
725 describe moduleName $ do
727 describe "Instances" $ do
728 prop "applicative" applicative
730 prop "sequence" sequence
732 describe "Stream parsing" $ do
733 prop "parseMany" parseMany
734 prop "parseMany2Events" parseMany2Events
735 prop "parseUnfold" parseUnfold
736 prop "parserSequence" parserSequence
738 describe "test for accumulator" $ do
739 prop "P.fromFold FL.sum = FL.sum" fromFold
740 prop "fromPure value provided" fromPure
741 prop "fromPure monadic value provided" fromEffect
742 prop "always fail" die
743 prop "always fail but monadic" dieM
745 describe "test for element parser" $ do
746 prop "peek = head with list length > 0" peekPass
747 prop "peek fail on []" peekFail
748 prop "eof pass on []" eofPass
749 prop "eof fail on non-empty list" eofFail
750 prop "first element exists and >= mid_value" satisfyPass
751 prop "check first element exists and satisfies predicate" satisfy
753 describe "test for sequence parser" $ do
754 prop "P.takeBetween m n = Prelude.take when len >= m and len <= n"
756 prop "P.takeBetween m n = Prelude.take when len >= m and len <= n and\
757 \fail otherwise" takeBetween
758 prop "P.take = Prelude.take" Main.take
759 prop "P.takeEQ = Prelude.take when len >= n" takeEQPass
760 prop "P.takeEQ = Prelude.take when len >= n and fail otherwise" Main.takeEQ
761 prop "P.takeGE n ls = ls when len >= n" takeGEPass
762 prop "P.takeGE n ls = ls when len >= n and fail otherwise" Main.takeGE
763 prop "lookAhead . take n >> lookAhead . take n = lookAhead . take n" lookAheadPass
764 prop "lookAhead . take n >> lookAhead . take n = lookAhead . take n, else fail" lookAhead
765 prop "P.takeWhile = Prelude.takeWhile" Main.takeWhile
766 prop "P.takeWhile1 = Prelude.takeWhile if taken something, else check why failed" takeWhile1
767 prop "P.groupBy = Prelude.head . Prelude.groupBy" groupBy
768 prop "groupByRolling" groupByRolling
769 prop "P.takeEndByOrMax = Prelude.take n (Prelude.takeWhile (not . predicate)" takeEndByOrMax
770 prop "many (P.wordBy ' ') = words'" wordBy
771 prop "parse 0, then 1, else fail" serialWith
772 prop "fail due to die as left parser" splitWithFailLeft
773 prop "fail due to die as right parser" splitWithFailRight
774 prop "fail due to die as both parsers" splitWithFailBoth
775 prop "parsed two lists should be equal" teeWithPass
776 prop "fail due to die as left parser" teeWithFailLeft
777 prop "fail due to die as right parser" teeWithFailRight
778 prop "fail due to die as both parsers" teeWithFailBoth
779 prop "P.takeWhile (<= half_mid_value) = Prelude.takeWhile half_mid_value" shortestPass
780 prop "pass even if die is left parser" shortestPassLeft
781 prop "pass even if die is right parser" shortestPassRight
782 prop "fail due to die as both parsers" shortestFailBoth
783 prop "P.takeWhile (<= mid_value) = Prelude.takeWhile (<= mid_value)" longestPass
784 prop "pass even if die is left parser" longestPassLeft
785 prop "pass even if die is right parser" longestPassRight
786 prop "fail due to die as both parsers" longestFailBoth
787 prop "P.many concatFold $ P.takeEndBy_ (== 1) FL.toList = Prelude.filter (== 0)" many
788 prop "[] due to parser being die" many_empty
789 prop "P.some concatFold $ P.takeEndBy_ (== 1) FL.toList = Prelude.filter (== 0)" some
790 prop "fail due to parser being die" someFail
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list