packages feed

sexpr-0.2.0: Codec/Sexpr/Tests.hs

module Main where

import Codec.Sexpr
import Codec.Sexpr.Parser
import Codec.Sexpr.Printer
import Test.QuickCheck
import Data.Monoid()
import Text.Show.Functions()
import qualified Data.Traversable as T
import qualified Data.Foldable as F
import Data.Char
import Data.Binary.Put
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as L
import Text.PrettyPrint (render)

import Text.Printf
import Control.Monad
import System.Environment
import System.Random
import System.IO
import Data.List

prop_atoms :: Int -> Bool
prop_atoms n = n == (unAtom $ atom n)

prop_foldMap :: (Int -> [Int]) -> Sexpr Int -> Bool
prop_foldMap f s = T.foldMapDefault f s == F.foldMap f s

prop_fmap :: (Int -> Int) -> Sexpr Int -> Bool
prop_fmap f s = T.fmapDefault f s == fmap f s

prop_readshow :: Sexpr Int -> Bool
prop_readshow s = (read . show $ s) == s

prop_readshowStr :: Sexpr String -> Bool
prop_readshowStr s = (readSexprString $ advancedString s) == s

prop_canonical_out :: Sexpr String -> Bool
prop_canonical_out s = (readSexprString $ canonicalString s) == s

prop_canonical_in :: Sexpr String -> Bool
prop_canonical_in s = (readCanonicalSexprString $ canonicalString s) == s

prop_put_canonical :: Sexpr String -> Bool
prop_put_canonical s = 
    (L.unpack . runPut $ putCanonical s) == canonicalString s

prop_put_canonicalBS :: Sexpr B.ByteString -> Bool
prop_put_canonicalBS s = 
    (L.unpack . runPut $ putCanonicalBS s) == (canonicalString $ fmap B.unpack s)

prop_atom_raw :: String -> Bool
prop_atom_raw s = (readSexprString $ Codec.Sexpr.Printer.raw s "") == atom s

prop_atom_token :: String -> Property
prop_atom_token s = canToken s ==> ((readSexprString s) == atom s)

prop_atom_hex :: String -> Property
prop_atom_hex s = canHex s ==> ((readSexprString $ render $ hex s) == atom s)

prop_atom_quote :: String -> Property
prop_atom_quote s = canQuote s ==> ((readSexprString $ render $ quote s) == atom s)

prop_atom_base64 :: String -> Bool
prop_atom_base64 s = (readSexprString $ render $ base64 s) == atom s 

instance Arbitrary B.ByteString where
    arbitrary = B.pack `fmap` arbitrary
    coarbitrary = undefined

instance Arbitrary Char where
  arbitrary     = choose (32,255) >>= \n -> return (chr n)
  coarbitrary n = variant (ord n)


main :: IO ()
main = do
  args <- fmap (drop 1) getArgs
  let n = if null args then 100 else read (head args)
  (results, passed) <- 
       liftM unzip $ mapM (\(s,a) -> printf "%-40s: " s >> a n) tests
  printf "Passed %d tests!\n" (sum passed)
  when (not . and $ results) $ fail "Not all tests passed!"

tests :: [(String, Int -> IO (Bool, Int))]
tests = [("Atom dis/assembly", mytest prop_atoms)
        ,("foldMap behaves as default", mytest prop_foldMap)
        ,("fmap behaves as default", mytest prop_fmap)
        ,("(read.show)==id | Int", mytest prop_readshow)
        ,("(read.show)==id | String", mytest prop_readshowStr)
        ,("canonical output", mytest prop_canonical_out)
        ,("canonical input", mytest prop_canonical_in)
        ,("efficient bytestring canonical", mytest prop_put_canonical)
        ,("efficient bytestring canonicalBS", mytest prop_put_canonicalBS)
        ,("token atom", mytest prop_atom_token)
        ,("hex atom", mytest prop_atom_hex)
        ,("quoted atom", mytest prop_atom_quote)
        ,("raw atom", mytest prop_atom_raw)
        ,("base64 atom", mytest prop_atom_base64)
        ]

------------------------------------------------------------------------
--
-- QC driver
-- copied from xmonad 0.8.1

debug :: Bool
debug = False

mytest :: Testable a => a -> Int -> IO (Bool, Int)
mytest a n = mycheck defaultConfig
    { configMaxTest=n
    , configEvery   = \nu _ -> let s = show nu in s ++ [ '\b' | _ <- s ] } a
 -- , configEvery= \n args -> if debug then show n ++ ":\n" ++ unlines args else [] } a

mycheck :: Testable a => Config -> a -> IO (Bool, Int)
mycheck config a = do
    rnd <- newStdGen
    mytests config (evaluate a) rnd 0 0 []

mytests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] -> IO (Bool, Int)
mytests config gen rnd0 ntest nfail stamps
    | ntest == configMaxTest config = done "OK," ntest stamps >> return (True, ntest)
    | nfail == configMaxFail config = done "Arguments exhausted after" ntest stamps >> return (True, ntest)
    | otherwise               =
      do putStr (configEvery config ntest (arguments result)) >> hFlush stdout
         case ok result of
           Nothing    ->
             mytests config gen rnd1 ntest (nfail+1) stamps
           Just True  ->
             mytests config gen rnd1 (ntest+1) nfail (stamp result:stamps)
           Just False ->
             putStr ( "Falsifiable after "
                   ++ show ntest
                   ++ " tests:\n"
                   ++ unlines (arguments result)
                    ) >> hFlush stdout >> return (False, ntest)
     where
      result      = generate (configSize config ntest) rnd2 gen
      (rnd1,rnd2) = split rnd0

done :: String -> Int -> [[String]] -> IO ()
done mesg ntest stamps = putStr ( mesg ++ " " ++ show ntest ++ " tests" ++ table )
  where
    table = display
            . map entry
            . reverse
            . sort
            . map pairLength
            . group
            . sort
            . filter (not . null)
            $ stamps

    display []  = ".\n"
    display [x] = " (" ++ x ++ ").\n"
    display xs  = ".\n" ++ unlines (map (++ ".") xs)

    pairLength xss@(xs:_) = (length xss, xs)
    pairLength [] = (0,undefined)
    entry (n, xs)         = percentage n ntest
                       ++ " "
                       ++ concat (intersperse ", " xs)

    percentage n m        = show ((100 * n) `div` m) ++ "%"

------------------------------------------------------------------------