packages feed

random-strings (empty) → 0.1.0.0

raw patch · 11 files changed

+692/−0 lines, 11 filesdep +QuickCheckdep +basedep +containerssetup-changed

Dependencies added: QuickCheck, base, containers, mtl, random, random-strings

Files

+ CHANGELOG view
@@ -0,0 +1,2 @@++
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, Michael Hatfield++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Michael Hatfield nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.markdown view
@@ -0,0 +1,32 @@+++## Package `random-strings`.++A simple way to generate random strings for testing and benchmarking.++A purely random character string is not always the best test case for testing+and benchmarking. These modules help generate random strings with preferences+for character sets and character properties.++Example:++    module Main ( main ) where++    import Test.RandomStrings++    iso_alpha = onlyAlpha randomChar8+    ascii_alphanum = onlyAlphaNum randomASCII++    -- print a list of 30 random alphanumeric strings between 5 and 25+    -- chars long.++    main = do+        words <- randomStringsLen (randomString ascii_alphanum) (5,25) 30+        mapM_ putStrLn words+++Build the example with `cabal build readme-example`.++Functions allow tuning strings for character class and toying with the+distribution of alphabetic and upper/lower-case characters.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ random-strings.cabal view
@@ -0,0 +1,93 @@++name:                   random-strings+version:                0.1.0.0++license:                BSD3+license-file:           LICENSE+copyright:              (c) 2016 Michael Hatfield++author:                 Michael Hatfield+maintainer:             github@michael-hatfield.com+homepage:               https://github.com/mikehat/random-strings+stability:              experimental+bug-reports:            https://github.com/mikehat/random-strings++synopsis:               Generate random strings with specific qualities+description:++    Useful for generating test/benchmark data, the 'Test.RandomStrings' module+    provides functions for generating random character strings in the ASCII+    range.  You can specify probabilities for the alphabet-range chars and+    capital case characters.+++category:               Text , Test+build-type:             Simple+cabal-version:          >=1.10++extra-source-files:       README.markdown+                        , CHANGELOG++source-repository head+    type:               git+    location:           git://github.com/mikehat/random-strings.git+    branch:             master++source-repository this+    type:               git+    location:           git://github.com/mikehat/random-strings.git+    branch:             master+    tag:                0.1++library++    exposed-modules:      Test.RandomStrings+                        , Test.RandomStrings.FlipCase++    other-modules:        Test.RandomStrings.Internal++    build-depends:        base >=4 && <5+                        , random >=1.0 && <2.0++    hs-source-dirs:     src+    default-language:   Haskell2010+++test-suite test-basics++    type:               exitcode-stdio-1.0+    main-is:            test-basics.hs++    build-depends:        base >=4 && <5+                        , random-strings+                        , QuickCheck+                        , mtl ++    hs-source-dirs:     tests+    default-language:   Haskell2010+++test-suite test-randomness++    type:               exitcode-stdio-1.0+    main-is:            test-randomness.hs++    build-depends:        base >=4 && <5+                        , random-strings+                        , QuickCheck+                        , mtl+                        , containers++    hs-source-dirs:     tests+    default-language:   Haskell2010+++executable readme-example++    main-is:            readme-example.hs++    build-depends:        base >=4 && <5+                        , random-strings++    hs-source-dirs:     tests+    default-language:   Haskell2010
+ src/Test/RandomStrings.hs view
@@ -0,0 +1,207 @@++{- |+ +Module      : Test.RandomStrings+Description : Generate random strings for testing++A way to generate random character strings for testing. Functions allow+tuning of strings, setting the probability of alphabetic and upper-case+characters in the resulting strings.++Hopefully this is useful for building test and benchmark cases that are+more meaningful than the overly random strings generated by libraries like+'Test.QuickCheck'. ++Note: /Please don't use this to generate passwords!/++Examples:++    Generate a 10-letter word with 1 in 10 upper-case characters++    > word <- randomWord' randomASCII (1%10) 10++    Generate a list of 500 strings from the printable ISO-8859-1 characters+    with random lengths beween 5 and 20 characters.++    > let iso_printable = randomString $ onlyPrintable randomChar8+    > strings <- randomStringsLen iso_printable (5,20) 500+    > +    > -- benchmarks ...+    >+++-}++module Test.RandomStrings+    (+    -- * Character generators+      randomChar+    , randomASCII+    , randomChar8+    +    -- * Specific character generators+    , onlyWith+    , onlyPrintable+    , onlyAlpha+    , onlyAlpha'+    , onlyAlphaNum+    , onlyUpper+    , onlyLower+    , randomClass++    -- * String generators+    , randomWord+    , randomWord'+    , randomString+    , randomString'++    -- * Sets of randomly generated strings+    , randomStrings+    , randomStringsLen+    )+where+++import           Data.Bool ( bool )+import           Data.Char+import           Data.Ratio+import           Control.Monad+import           System.Random++import           Test.RandomStrings.Internal++++-- | Generate a random Char+randomChar :: IO Char+randomChar = getStdRandom $ random+++-- | Generate a random ASCII (7-bit) char in the printable range.+randomASCII :: IO Char+randomASCII = getStdRandom $ randomR (chr 0,chr 127)+++-- | Generate a random ISO-8859-1 (8-bit) char+randomChar8 :: IO Char+randomChar8 = getStdRandom $ randomR (chr 0,chr 255)+++-- | Random character passing a test+onlyWith+    :: (Char -> Bool)   -- ^ predicate, like 'isAlpha'+    -> IO Char          -- ^ random char generator, like 'randomChar' or 'randomASCII' or 'randomChar8'+    -> IO Char+onlyWith p gen = gen >>= \c -> if p c then return c else onlyWith p gen+++-- | Supply a random printable character.+onlyPrintable :: IO Char -> IO Char+onlyPrintable = onlyWith isPrint+++-- | Generate a random printable non-alphabet char.+onlyNonAlpha :: IO Char -> IO Char+onlyNonAlpha = onlyWith (not . isAlpha) . onlyPrintable+++-- | Generate a random alphabetic char.+onlyAlpha :: IO Char -> IO Char+onlyAlpha = onlyWith isAlpha+++-- | Generate a random alphabetic char with a probability of being upper-case.+onlyAlpha'+    :: Rational     -- ^ range 0 to 1; chance of being an upper+    -> IO Char      -- ^ random char generator; 'randomChar', 'randomASCII', 'randomChar8'+    -> IO Char+onlyAlpha' r gen =  randomClass r (onlyUpper gen) (onlyLower gen)+++-- | Generate an alphanumeric char.+onlyAlphaNum :: IO Char -> IO Char+onlyAlphaNum = onlyWith isAlphaNum++-- | Randomly generate one of two character types+randomClass+    :: Rational     -- ^ range 0 to 1; chance of using the first generator+    -> IO Char      -- ^ first generator; used if the random value is @True@+    -> IO Char      -- ^ second generator; used if the random value is @False@+    -> IO Char+randomClass r t f = randomBool r >>= bool f t+++-- | Generate a random upper-case letter.+onlyUpper :: IO Char -> IO Char+onlyUpper = onlyWith isUpper +++-- | Generate a random lower-case letter.+onlyLower :: IO Char -> IO Char+onlyLower = onlyWith isLower++++-- | Generate a random string of alphabetic characters.+randomWord+    :: IO Char      -- ^ random char generator; 'randomChar' or 'randomASCII' or 'randomChar8'+    -> Int          -- ^ length+    -> IO String+randomWord gen len = replicateM len $ onlyAlpha gen++randomWord'+    :: IO Char      -- ^ random char generator; 'randomChar' or 'randomASCII' or 'randomChar8'+    -> Rational     -- ^ range 0 to 1; fraction of upper-case letters+    -> Int          -- ^ length+    -> IO String+randomWord' gen r len = replicateM len $ onlyAlpha' r gen+++-- | Generate a random string+randomString+    :: IO Char      -- ^ random char generator; eg. 'randomChar8' or @onlyAlpha randomASCII@+    -> Int          -- ^ length+    -> IO String+randomString = flip replicateM++-- | Generate a random string of printable characters with a balance of+--   alphabetic and upper-case characters.+randomString'+    :: IO Char      -- ^ random char generator; 'randomChar' or 'randomASCII' or 'randomChar8'+    -> Rational     -- ^ range 0 to 1; fraction of alphabetic characters+    -> Rational     -- ^ range 0 to 1; fraction of upper-case letters+    -> Int          -- ^ length+    -> IO String+randomString' gen ra ru len = replicateM len $ randomClass ra (randomClass ru (onlyUpper gen) (onlyLower gen)) (onlyNonAlpha gen)+++-- | Generate a list of strings of uniform length.+--+--   > randomStrings (randomString (onlyAlpha randomChar8) 20) 50+--+--   will build a list of 50 alphabetical strings, each 20 characters long.+--+randomStrings+    :: IO String    -- ^ random string generator, eg. 'randomString randomAlpha 20'+    -> Int          -- ^ list length+    -> IO [String]+randomStrings = flip replicateM+++-- | Generate a list of strings of variable length.+--   +--   Similar to 'randomStrings', but generates strings with random length.+--   Example:+--+--   > randomStringsLen (randomString' randomASCII (3%4) (1%8)) (10,30) 100+--  +--   Returns a list of 100 strings that are between 10 and 30 characters,+--   with 3/4 of them being alphabetical and 1/8 of those being upper-case.+--+randomStringsLen+    :: (Int -> IO String)   -- ^ random string generator, eg. @randomString randomAlpha@+    -> (Int,Int)            -- ^ range for string length+    -> Int                  -- ^ list length+    -> IO [String]+randomStringsLen gen range len = replicateM len $ getStdRandom (randomR range) >>= gen+
+ src/Test/RandomStrings/FlipCase.hs view
@@ -0,0 +1,60 @@++++{- | ++module      : Test.RandomStrings.FlipCase+description : Randomly flip case of strings++A helper for randomly changing the case in strings. Useful for generating+test-cases for case-insensitive matching.++-}++module Test.RandomStrings.FlipCase+    ( flipCase+    , flipCaseString+    , randomFlipCase+    , randomFlipCaseString+    )+where+++import           Data.Bool ( bool )+import           Data.Char+import           Data.Ratio+import           Control.Monad+import           System.Random++import           Test.RandomStrings.Internal++++-- | Toggle the case of a @Char@.+flipCase :: Char -> Char+flipCase c+    | isUpper c = toLower c+    | isLower c = toUpper c+    | otherwise = c+++-- | Toggle character case for a @String@.+flipCaseString :: String -> String+flipCaseString = map flipCase+++-- | Randomly flip the case of a @Char@.+randomFlipCase+    :: Rational     -- ^ range 0 to 1; chance of flipping case+    -> Char         -- ^ @Char@ to flip+    -> IO Char+randomFlipCase r c = randomBool r >>= bool (return c) (return $ flipCase c)+++-- | Randomly flip the case of a @String@.+randomFlipCaseString+    :: Rational     -- ^ range 0 to 1; chance of flipping case+    -> String       -- ^ original @String@+    -> IO String+randomFlipCaseString r = mapM (randomFlipCase r)+
+ src/Test/RandomStrings/Internal.hs view
@@ -0,0 +1,22 @@++{- |+    module:         Test.RandomStrings.Internal++-}++module Test.RandomStrings.Internal+where+++import           Data.Ratio+import           System.Random++++-- | Flip a 'loaded' coin with a certain chance of being True+randomBool+    :: Rational     -- ^ range 0 to 1; chance of being true+    -> IO Bool      +randomBool r = do+    roll <- getStdRandom $ randomR (1,denominator r)+    return $ roll <= numerator r
+ tests/readme-example.hs view
@@ -0,0 +1,13 @@+++module Main ( main ) where++import Test.RandomStrings++iso_alpha = onlyAlpha randomChar8+ascii_alphanum = onlyAlphaNum randomASCII++main = do+    words <- randomStringsLen (randomString ascii_alphanum) (5,25) 30+    mapM_ putStrLn words+
+ tests/test-basics.hs view
@@ -0,0 +1,112 @@++{-++These are some simple tests that may seem so obvious they shouldn't be+necessary. There is nothing here to verify proper randomness, just that the+mechanics like string lengths and character classes are working properly.++-}++module Main ( main ) where++import           Test.QuickCheck+import           Test.QuickCheck.Monadic++import           Control.Monad.State+import           System.IO+import           System.Exit++import           Data.Char++import           Test.RandomStrings++++extraTests = stdArgs { maxSuccess = 1000 }++main = do+    success <- flip execStateT True $ do+        liftTest "randomASCII"      extraTests prop_randomASCII+        liftTest "randomChar8"      extraTests prop_randomChar8+        liftTest "randomAlphaChar"  extraTests prop_randomAlphaChar+        liftTest "randomAlphaChar8" extraTests prop_randomAlphaChar8+        liftTest "randomAlphaASCII" extraTests prop_randomAlphaASCII+        liftTest "randomUpperASCII" extraTests prop_randomUpperASCII+        liftTest "randomWord"       stdArgs    prop_randomWord+        liftTest "randomStrings"    stdArgs    prop_randomStrings+        liftTest "randomStringsLen" stdArgs    prop_randomStringsLen++    if success then exitSuccess else exitFailure+    ++indent = replicate 4 ' '++liftTest :: (Testable prop) => String -> Args -> prop -> StateT Bool IO ()+liftTest name args prop = do+    liftIO $ putStr $ take 25 $ concat [ indent , name , ":" , repeat ' ' ]+    r <- liftIO $ quickCheckWithResult args prop+    case r of Success {} -> return ()+              _          -> put False+++++in_range :: (Ord a) => (a,a) -> a -> Bool+in_range (lbound,ubound) a = a >= lbound && a <= ubound++is_ascii :: Char -> Bool+is_ascii = in_range (0,127) . fromEnum++is_char8 :: Char -> Bool+is_char8 = in_range (0,255) . fromEnum+++prop_randomASCII :: Property+prop_randomASCII = monadicIO $ run randomASCII >>= assert . is_ascii++prop_randomChar8 :: Property+prop_randomChar8 = monadicIO $ run randomChar8 >>= assert . is_char8++prop_randomAlphaChar :: Property+prop_randomAlphaChar = monadicIO $ run (onlyAlpha randomChar) >>= assert . isAlpha++prop_randomAlphaChar8 :: Property+prop_randomAlphaChar8 = monadicIO $ run (onlyAlpha randomChar8) >>= assert . isAlpha++prop_randomAlphaASCII :: Property+prop_randomAlphaASCII = monadicIO $ run (onlyAlpha randomASCII) >>= assert . isAlpha++prop_randomUpperASCII :: Property+prop_randomUpperASCII = monadicIO $ run (onlyUpper randomASCII) >>= assert . isUpper++prop_randomWord :: Int -> Property+prop_randomWord len = monadicIO $ run (randomWord randomChar8 $ len') >>= assert . (== len') . length+    where len' = len `mod` 500 -- max string length in 0 to 499, please++prop_randomStrings :: Int -> Int -> Property+prop_randomStrings str_len list_len = monadicIO $ do+    let str_len' = str_len `mod` 100+        list_len' = list_len `mod` 200+    strings <- run $ randomStrings (randomString randomChar str_len') list_len'++    -- all strings are the right length+    assert $ all (== str_len') $ map length strings++    -- the list is the right length+    assert $ length strings == list_len'++prop_randomStringsLen :: (Int,Int) -> Int -> Property+prop_randomStringsLen (min_len,max_len) list_len = monadicIO $ do+    let min_len' = min_len `mod` 100+        max_len' = max_len `mod` 100+        list_len' = list_len `mod` 200+        limits' = (min_len',max_len')+    pre $ min_len' <= max_len'+    strings <- run $ randomStringsLen (randomString randomChar) limits' list_len'++    -- all strings length are within the range+    assert $ all (in_range limits') $ map length strings++    -- the list is the right length+    assert $ length strings == list_len'+
+ tests/test-randomness.hs view
@@ -0,0 +1,119 @@+++{-++These tests are not the most correct tests of randomness and are not intended+to put the random number generator in the spotlight. The hope is that any gross+coding error in this library will be caught.++If there are some more rigorous tests that are easy to implement without adding+too many dependencies, please send suggestions. Even a more mathematical+approach to static numbers for test repetitions and failure limits would be a+welcome improvement. I'm not about to drag out my moldy old textbooks and do+it myself. I'd certainly rather do that than my day job ...++-}++module Main ( main ) where++import           Test.QuickCheck+import           Test.QuickCheck.Monadic++import           Control.Monad+import           Control.Monad.State+import           System.IO+import           System.Exit++import           Data.Char+import           Data.Ord ( max )+import           Data.Ratio ( (%) )+import qualified Data.Map.Strict as M++import           Test.RandomStrings+import           Test.RandomStrings.FlipCase+++extraTests = stdArgs { maxSuccess = 1000 }+++main = do+    success <- flip execStateT True $ do+        liftTest "randomAlpha'"     stdArgs prop_randomAlpha'+        liftTest "randomString'"    stdArgs prop_randomString'+        liftTest "randomStringsLen" stdArgs prop_randomStringsLen+        liftTest "randomFlipCase"   stdArgs prop_randomFlipCase+    if success then exitSuccess else exitFailure++indent = replicate 4 ' '++liftTest :: (Testable prop) => String -> Args -> prop -> StateT Bool IO ()+liftTest name args prop = do+    liftIO $ putStr $ take 25 $ concat [ indent , name , ":" , repeat ' ' ]+    r <- liftIO $ quickCheckWithResult args prop+    case r of Success {} -> return ()+              _          -> put False+++in_range :: (Ord a) => (a,a) -> a -> Bool+in_range (lbound,ubound) a = a >= lbound && a <= ubound++++prop_randomAlpha' :: Property+prop_randomAlpha' = monadicIO $ do+    sample <- liftIO $ replicateM 1000 $ onlyAlpha' (1%10) randomChar8++    -- all characters are alphabetic+    assert $ all isAlpha sample++    -- there are not too many upper-case chars+    assert $ (length $ filter isUpper sample) < 200 -- should be around 100++    -- to single char is too frequent+    let freq = foldr (\c -> M.insertWith (+) c 1) M.empty sample+        max_freq = M.foldl max 0 freq+    assert $ max_freq < 60 -- why? made it up. Average max is around 30+++prop_randomString' :: Property+prop_randomString' = monadicIO $ do+    sample <- liftIO $ randomString' randomASCII (9%10) (1%10) 1000++    -- there are about the right number of alphabetic chars+    assert $ (length $ filter isAlpha sample) > 800 -- should be around 900++    -- there are the right number of upper-case chars+    assert $ (length $ filter isUpper sample) < 180 -- should be around 90++    -- no single char appears too often+    let freq = foldr (\c -> M.insertWith (+) c 1) M.empty sample+        max_freq = M.foldl max 0 freq+    assert $ max_freq < 80 -- why? made it up. Average max around 40+++prop_randomStringsLen :: Property+prop_randomStringsLen = monadicIO $ do+    samples <- liftIO $ randomStringsLen (randomWord randomChar) (5,95) 1000++    -- strings are the right length+    assert $ all (in_range (5,95) . length ) samples++    -- string lengths have a nice distribution+    let freq = foldr (\c -> M.insertWith (+) c 1) M.empty $ map length samples+        max_freq = M.foldl max 0 freq+    assert $ max_freq < 40 -- why? made it up. Average max around 20+    +prop_randomFlipCase :: Property+prop_randomFlipCase = monadicIO $ do+    original <- run $ randomWord (onlyUpper randomASCII) 100+    sample <- run $ randomFlipCaseString (1%2) original++    -- correct length+    assert $ length sample == 100++    -- only case-flipping happened+    assert $ (map toLower original) == (map toLower sample)++    -- about the right number of chars are flipped+    -- liftIO $ putStrLn $ show $ length $ filter isLower sample+    assert $ in_range (30,70) $ length $ filter isLower sample