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 +2/−0
- LICENSE +30/−0
- README.markdown +32/−0
- Setup.hs +2/−0
- random-strings.cabal +93/−0
- src/Test/RandomStrings.hs +207/−0
- src/Test/RandomStrings/FlipCase.hs +60/−0
- src/Test/RandomStrings/Internal.hs +22/−0
- tests/readme-example.hs +13/−0
- tests/test-basics.hs +112/−0
- tests/test-randomness.hs +119/−0
+ 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