{-# OPTIONS_GHC -fno-warn-orphans #-} module ParserTest ( parserTests ) where import Data.Attoparsec.Text ( parseOnly ) import Data.Monoid import Data.Text (Text) import Database.PostgreSQL.Query.SqlBuilder import Database.PostgreSQL.Query.TH.SqlExp import Test.QuickCheck.Arbitrary.Generic import Test.QuickCheck.Assertions import Test.QuickCheck.Instances () import Test.QuickCheck.Modifiers import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck import Test.Tasty.TH import qualified Data.Text as T noSeqSpace :: [Rope] -> [Rope] noSeqSpace ((RSpaces a):(RSpaces b):xs) = noSeqSpace $ (RSpaces $ a + b):xs noSeqSpace (x:xs) = x:(noSeqSpace xs) noSeqSpace [] = [] newtype RopeList = RopeList [Rope] deriving (Ord, Eq, Show) instance Arbitrary RopeList where arbitrary = resize 10 $ (RopeList . noSeqSpace . getNonEmpty) <$> arbitrary instance Arbitrary FieldOption where arbitrary = genericArbitrary shrink = genericShrink wordAlpha :: [Text] wordAlpha = map T.singleton $ ['A'..'Z'] ++ ['a'..'z'] ++ "!@$%&*()[];|{}<>=" stringAlpha :: [Text] stringAlpha = wordAlpha ++ ["''", "\\'", " "] identAlpha :: [Text] identAlpha = wordAlpha ++ ["\"\"", " "] intAlpha :: [Text] intAlpha = wordAlpha ++ [" "] instance Arbitrary Rope where arbitrary = oneof [ RLit <$> stringLit , RLit <$> idLit , RInt <$> arbitrary <*> ropeInt , RPaste <$> ropePaste , RComment <$> comment , RComment <$> bcomment , RSpaces <$> spaces , RLit <$> wordLit ] where selems = resize 5 . listOf . elements selems1 = resize 5 . listOf1 . elements stringLit = do x <- selems stringAlpha return $ "'" <> mconcat x <> "'" idLit = do x <- selems identAlpha return $ "\"" <> mconcat x <> "\"" ropeInt = do x <- selems intAlpha return $ "#{" <> mconcat x <> "}" ropePaste = do x <- selems1 intAlpha return $ "^{" <> mconcat x <> "}" comment = do s <- selems wordAlpha return $ "--" <> mconcat s bcomment = do n <- selems wordAlpha return $ "/*" <> mconcat n <> "*/" spaces = suchThat arbitrary (>= 1) wordLit = fmap mconcat $ selems1 wordAlpha flattenRope :: [Rope] -> Text flattenRope = mconcat . map f where f (RLit t) = t f (RComment c) = case T.uncons c of (Just ('-', _)) -> c <> "\n" _ -> c f (RSpaces s) = mconcat $ replicate s " " f (RInt FieldDefault t) = "#{" <> quoteBrace t <> "}" f (RInt FieldMasked t) = "#?{" <> quoteBrace t <> "}" f (RPaste t) = "^{" <> quoteBrace t <> "}" quoteBrace = T.replace "}" "\\}" prop_RopeParser :: RopeList -> Result prop_RopeParser (RopeList rope) = (Right $ squashRope rope) ==? (fmap squashRope $ parseOnly ropeParser $ flattenRope rope) expectParser :: (Eq a, Show a) => ([Rope] -> a) -> (a, Text) -> Assertion expectParser predicate (a, raw) = do rope <- either fail return $ parseOnly ropeParser raw a @=? predicate rope case_expectLiterals :: Assertion case_expectLiterals = mapM_ (expectParser takeLits) [ ("hello hello", "hello hello") , ("'hello hello '", "'hello hello '") , (" SELECT ", " SELECT -- ") , ("xxx ' '' '", "xxx ' '' '") , ("xxx xxx xxx", "xxx xxx -- \n xxx") , (" ' ''\\'' ", " ' ''\\'' ") , ("'''\\'''''\\'' ", "'''\\'''''\\'' ") , (" \" ident \" ", " \" ident \" ") , (" one - two -> plus ", " one - \n two -> plus -- \n") , ("xxx ", "xxx /* this must eliminate */") , ("xxx xxx", "xxx /* bla /* nested */ \n comment */ xxx") ] where takeLits = mconcat . map takeLit takeLit :: Rope -> Text takeLit (RLit t) = t takeLit (RSpaces _) = " " takeLit _ = "" case_expectComments :: Assertion case_expectComments = mapM_ (expectParser takeComments) [ ("", "hello hello") , ("", "'hello hello '") , ("-- line comment", " SELECT -- line comment") , ("/* comment */", "/* comment */ ' /* */ '' -- '") ] where takeComments = mconcat . map takeComment takeComment :: Rope -> Text takeComment (RComment t) = t takeComment _ = "" case_expectInterpolation :: Assertion case_expectInterpolation = mapM_ (expectParser takeInterpol) [ ("", " hello hello ") , ("something", " hello #{something} happend") , ("something", " hello ^{something} happend") , ("something", " hello #?{something} happend") , ("", " 'hello #{something} happend' ") , ("", " 'hello ^{something} happend' ") , ("", " 'hello #?{something} happend' ") , ("", " \"hello #{something} happend\" ") , ("", " \"hello ^{something} happend\" ") , ("", " \"hello #?{something} happend\" ") , ("", " -- hello #{something} happend ") , ("", " -- hello ^{something} happend' ") , ("", " -- hello #?{something} happend' ") , ("", " /* hello #{something} happend */ ") , ("", " /* hello ^{something} happend */ ") , ("", " /* hello #?{something} happend */ ") ] where takeInterpol = mconcat . map go go :: Rope -> Text go (RInt _ t) = t go (RPaste t) = t go _ = "" parserTests :: TestTree parserTests = $testGroupGenerator