├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.rdoc ├── Rakefile ├── data ├── doc │ ├── policy.txt │ ├── rushcheck.thtml │ └── spec.txt └── examples │ ├── candy.rb │ ├── printf.rb │ ├── proc.rb │ ├── pythagoras.rb │ ├── roguetile.rb │ ├── rspec │ ├── stack.rb │ └── stack_spec.rb │ └── sample.rb ├── lib ├── rushcheck.rb └── rushcheck │ ├── arbitrary.rb │ ├── array.rb │ ├── assertion.rb │ ├── bool.rb │ ├── config.rb │ ├── error.rb │ ├── float.rb │ ├── gen.rb │ ├── guard.rb │ ├── hash.rb │ ├── integer.rb │ ├── proc.rb │ ├── property.rb │ ├── random.rb │ ├── result.rb │ ├── string.rb │ ├── testable.rb │ ├── testoptions.rb │ ├── testresult.rb │ └── version.rb ├── rushcheck.gemspec └── test ├── helper.rb ├── prelude.rb ├── spec_array.rb ├── spec_assertion.rb ├── spec_gen.rb ├── spec_random.rb ├── test_arbitrary.rb ├── test_array.rb ├── test_assertion.rb └── test_rushcheck.rb /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | 14 | # jeweler generated 15 | pkg 16 | 17 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 18 | # 19 | # * Create a file at ~/.gitignore 20 | # * Include files you want ignored 21 | # * Run: git config --global core.excludesfile ~/.gitignore 22 | # 23 | # After doing this, these files will be ignored in all your git projects, 24 | # saving you from having to 'pollute' every project you touch with them 25 | # 26 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 27 | # 28 | # For MacOS: 29 | # 30 | .DS_Store 31 | # 32 | # For TextMate 33 | #*.tmproj 34 | #tmtags 35 | # 36 | # For emacs: 37 | *~ 38 | \#* 39 | .\#* 40 | # 41 | # For vim: 42 | *.swp 43 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | # Add dependencies required to use your gem here. 3 | # Example: 4 | # gem "activesupport", ">= 2.3.5" 5 | 6 | # Add dependencies to develop your gem here. 7 | # Include everything needed to run rake, tests, features, etc. 8 | group :development do 9 | gem "rdoc", ">= 3.5.3" 10 | gem "shoulda", ">= 0" 11 | gem "bundler", "~> 1.0.0" 12 | gem "jeweler", "~> 1.5.2" 13 | gem "rcov", ">= 0" 14 | end 15 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | git (1.2.5) 5 | jeweler (1.5.2) 6 | bundler (~> 1.0.0) 7 | git (>= 1.2.5) 8 | rake 9 | rake (0.8.7) 10 | rcov (0.9.9) 11 | rdoc (3.5.3) 12 | shoulda (2.11.3) 13 | 14 | PLATFORMS 15 | ruby 16 | 17 | DEPENDENCIES 18 | bundler (~> 1.0.0) 19 | jeweler (~> 1.5.2) 20 | rcov 21 | rdoc (>= 3.5.3) 22 | shoulda 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2011 Daisuke IKEGAMI 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = RushCheck library - a random test library for ruby 2 | 3 | RushCheck library is a random testing library/tools for ruby 4 | which are imported from QuickCheck library in Haskell. 5 | 6 | QuickCheck has been developed by Koen Claessen and John Hughes in 2000. 7 | http://www.cs.chalmers.se/~rjmh/QuickCheck/ 8 | http://hackage.haskell.org/package/QuickCheck 9 | 10 | Check out directories both data/rushcheck/doc and rdoc for details. 11 | 12 | == Current status is alpha 13 | 14 | Less documents/examples, no gem. There is a gem version 0.8, but, 15 | it is not recommended to install because we will change the API. 16 | 17 | == An example 18 | 19 | Let's see data/examples/sample.rb 20 | The first example is here: 21 | 22 | require 'rushcheck' 23 | 24 | # assert_sort_one should be true 25 | def assert_sort_one 26 | RushCheck::Assertion.new(Integer) { |x| 27 | [x].sort == [x] 28 | }.check 29 | end 30 | 31 | We will check that for all integer x, the array [x] does not changed after it is sorted. 32 | Here is a session with irb: (not installed, but downloaded the source code from the repository) 33 | 34 | % irb -I lib -I data/examples -r sample 35 | irb(main):001:0> assert_sort_one 36 | OK, passed 100 tests. 37 | ==> true 38 | irb(main):002:0> 39 | 40 | The above test passed as expected. Another example when we will meet a bug in the code. 41 | The property that sorting does not change arrays is explicitly *false*. 42 | 43 | # however, assert_sort_two is not true generally, 44 | # and RushCheck finds a counter example. 45 | def assert_sort_two 46 | RushCheck::Assertion.new(Integer, Integer) { |x, y| 47 | ary = [x, y] 48 | ary.sort == ary 49 | }.check 50 | end 51 | 52 | Continue the irb session: 53 | 54 | irb(main):002:0> assert_sort_two 55 | Falsifiable, after 2 tests: 56 | [2, 0] 57 | => false 58 | irb(main):003:0> 59 | 60 | When x <= y in the test, the sorted array [x, y].sort equals the original array [x, y]. 61 | However, if x > y then the sorted array differs the original. 62 | We see the counter example [2, 0] thanks to random testing. 63 | 64 | What we have studied from the above two examples: 65 | The method RushCheck::Assertion.new takes classes as variables, and has a block 66 | with the binded variables respectively; The block should return _true_ or _false_; 67 | Finally the method _check_ executes the random testing and shows the result. 68 | 69 | == More further 70 | 71 | Specification of the program should be provided as properties. In each property, 72 | we write what has to be satisfied. RushCheck helps to test the properties with 73 | 100 (as the default) tests by random generated test cases. We can change the 74 | number of tests easily as follows; 75 | 76 | # 1000 tests instead 77 | c = RushCheck::Config.new(1000) 78 | RushCheck::Assertion(Integer) { |x| 79 | ... # the property of the test 80 | }.check(c) 81 | 82 | The block of the property should be return either _true_ or _false_. Therefore 83 | the assertion method in Test/Unit should not be written in the block because this 84 | returns nil object. 85 | 86 | # NG case 87 | RushCheck::Assertion(Integer) { |x| 88 | assertion_equal x+x, 2*x # NG because this returns nil 89 | }.check 90 | 91 | On the other hand, the check method in RushCheck returns either true or false if 92 | the test does not throw any exception. Then we can combine RushCheck and Test/Unit 93 | like this; 94 | 95 | # OK 96 | assertion_equal true, 97 | RushCheck::Assertion(Integer) { |x| 98 | x+x == 2*x 99 | }.check 100 | 101 | == Contributing to rushcheck 102 | 103 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet 104 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it 105 | * Fork the project 106 | * Start a feature/bugfix branch 107 | * Commit and push until you are happy with your contribution 108 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 109 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 110 | 111 | == Copyright 112 | 113 | Copyright (c) 2006-2011 Daisuke IKEGAMI. See LICENSE.txt (The MIT license) for 114 | further details. 115 | 116 | Project webpage 117 | http://rushcheck.rubyforge.org 118 | 119 | Git repository 120 | http://github.com/IKEGAMIDaisuke/rushcheck 121 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | require './lib/rushcheck/version.rb' 4 | begin 5 | Bundler.setup(:default, :development) 6 | rescue Bundler::BundlerError => e 7 | $stderr.puts e.message 8 | $stderr.puts "Run `bundle install` to install missing gems" 9 | exit e.status_code 10 | end 11 | require 'rake' 12 | 13 | require 'jeweler' 14 | Jeweler::Tasks.new do |gem| 15 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options 16 | gem.name = "rushcheck" 17 | gem.homepage = "http://github.com/IKEGAMIDaisuke/rushcheck" 18 | gem.license = "MIT" 19 | gem.summary = "Random testing framework for Ruby" 20 | gem.description = "Specification based random testing framework like QuickCheck in Haskell" 21 | gem.email = "ikegami.da@gmail.com" 22 | gem.authors = ["Daisuke IKEGAMI"] 23 | gem.version = RushCheck::Version::STRING 24 | # Include your dependencies below. Runtime dependencies are required when using your gem, 25 | # and development dependencies are only needed for development (ie running rake tasks, tests, etc) 26 | # gem.add_runtime_dependency 'jabber4r', '> 0.1' 27 | # gem.add_development_dependency 'rspec', '> 1.2.3' 28 | end 29 | Jeweler::RubygemsDotOrgTasks.new 30 | 31 | require 'rake/testtask' 32 | Rake::TestTask.new(:test) do |test| 33 | test.libs << 'lib' << 'test' 34 | test.pattern = 'test/**/test_*.rb' 35 | test.verbose = true 36 | end 37 | 38 | require 'rcov/rcovtask' 39 | Rcov::RcovTask.new do |test| 40 | test.libs << 'test' 41 | test.pattern = 'test/**/test_*.rb' 42 | test.verbose = true 43 | end 44 | 45 | task :default => :test 46 | 47 | require 'rake/rdoctask' 48 | Rake::RDocTask.new do |rdoc| 49 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 50 | 51 | rdoc.rdoc_dir = 'rdoc' 52 | rdoc.title = "rushcheck #{version}" 53 | rdoc.rdoc_files.include('README*') 54 | rdoc.rdoc_files.include('lib/**/*.rb') 55 | end 56 | -------------------------------------------------------------------------------- /data/doc/policy.txt: -------------------------------------------------------------------------------- 1 | 2006-07-26 start Daisuke IKEGAMI 2 | 2006-08-09 add 3 | 4 | porting policy 5 | -------------- 6 | 7 | In this document, I compare between the original Haskell 8 | implementation in ghc and RushCheck. 9 | 10 | 1. Haskell98 System.Random 11 | 12 | 1.1 type, type classes and functions in System.Random 13 | 14 | class RandomGen g where 15 | next :: g -> (Int, g) 16 | split :: g -> (g, g) 17 | genRange :: g -> (Int, Int) 18 | 19 | data StdGen = StdGen Int Int 20 | instance RandomGen StdGen 21 | instance Show StdGen 22 | instance Read StdGen 23 | 24 | mkStdGen :: Int -> StdGen 25 | 26 | class Random a where 27 | random :: RandomGen g => g -> (a, g) 28 | randomR :: RandomGen g => (a, a) -> g -> (a, g) 29 | randoms :: RandomGen g => g -> [a] 30 | randomRs :: RandomGen g => (a, a) -> g -> [a] 31 | randomIO :: IO a 32 | randomRIO :: (a, a) -> IO a 33 | instance Random Int 34 | instance Random Char 35 | instance Random Bool 36 | instance Random Integer 37 | instance Random Double 38 | instance Random Float 39 | 40 | getStdRandom :: (StdGen -> (a, StdGen)) -> IO a 41 | getStdGen :: IO StdGen 42 | setStdGen :: StdGen -> IO () 43 | newStdGen :: IO StdGen 44 | 45 | 1.2 porting policy for System.Random in Haskell to Ruby 46 | 47 | Haskell ruby 48 | System.Random ---> rushcheck/random.rb 49 | 50 | Haskell ruby 51 | class RandomGen ---> module RandomGen 52 | for instance methods 53 | 54 | 'method RandomGen' is assumed to be included for making instance 55 | methods. 56 | 57 | Haskell 58 | next :: g -> (Int, g) 59 | split :: g -> (g, g) 60 | genRange :: g -> (Int, Int) 61 | ---> 62 | Ruby 63 | RandomGen#gen_next :: [Fixnum, SELF] 64 | RandomGen#split :: [SELF, SELF] 65 | RandomGen#gen_range :: [Fixnum, Int] 66 | 67 | - because 'next' is a reserved word of ruby, change it to 'gen_next'. 68 | - SELF is the class which includes the module RandomGen. 69 | - Haskell's Int is similar to Ruby's Fixnum 70 | 71 | Haskell 72 | data StdGen = StdGen Int Int 73 | instance RandomGen StdGen 74 | instance Show StdGen 75 | instance Read StdGen 76 | mkStdGen :: Int -> StdGen 77 | ---> 78 | Ruby 79 | class StdGen 80 | StdGen#initialize(left::Fixnum=nil, right::Fixnum = nil) 81 | StdGen includes module RandomGen 82 | StdGen#to_s 83 | 84 | - read is not implemented yet (maybe unnecessary?) 85 | - StdGen requires two integers in default, but we can also create 86 | StdGen with only one integer by mkStdGen. Therefore, 87 | StdGen#initialize takes one/two integers. In addition, we can make 88 | StdGen without any integers but they are generated randomly. 89 | 90 | Haskell 91 | class Random a where 92 | random :: RandomGen g => g -> (a, g) 93 | randomR :: RandomGen g => (a, a) -> g -> (a, g) 94 | randoms :: RandomGen g => g -> [a] 95 | randomRs :: RandomGen g => (a, a) -> g -> [a] 96 | randomIO :: IO a 97 | randomRIO :: (a, a) -> IO a 98 | ---> 99 | Ruby 100 | module HsRandom 101 | HsRandom#random 102 | (gen::RandomGen, lo::HsRandom=nil, hi::HsRandom=nil) 103 | :: [HsRandom, RandomGen] 104 | HsRandom#random_array 105 | (gen::RandomGen, len::Integer=nil, 106 | lo::HsRandom=nil, hi::HsRandom=nil) 107 | :: [HsRandom, RandomGen] 108 | HsRandom#random_std(lo::HsRandom=nil, hi::HsRandom=nil) 109 | private 110 | HsRandom#random_range 111 | (gen::RandomGen, lo::HsRandom=nil, hi::HsRandom=nil) 112 | :: [HsRandom, RandomGen] 113 | 114 | Haskell 115 | instance Random Int 116 | instance Random Integer 117 | ---> 118 | Ruby 119 | class Integer includes the module HsRandom 120 | why not Fixnum and Bignum? 121 | 122 | Haskell 123 | instance Random Char 124 | --> 125 | Ruby 126 | class String includes the module HsRandom 127 | 128 | Haskell 129 | instance Random Double 130 | instance Random Float 131 | --> 132 | Ruby 133 | class Float includes the module HsRandom 134 | 135 | Haskell 136 | instance Random Bool 137 | --> 138 | Ruby 139 | nop 140 | why not TrueClass and FalseClass? 141 | but what is the randomness of TrueClass? 142 | 143 | Haskell 144 | getStdRandom :: (StdGen -> (a, StdGen)) -> IO a 145 | --> 146 | Ruby 147 | nop 148 | 149 | Haskell 150 | newStdGen :: IO StdGen 151 | setStdGen :: StdGen -> IO () 152 | getStdGen :: IO StdGen 153 | --> 154 | Ruby 155 | class TheStdGen (singleton class) 156 | 157 | 2.1 Test.QuickCheck 158 | 159 | quickCheck :: Testable a => a -> IO () -- :: prop -> IO () 160 | verboseCheck :: Testable a => a -> IO () -- :: prop -> IO () 161 | test :: Testable a => a -> IO () -- :: prop -> IO () 162 | 163 | Config(..) -- :: * 164 | data Config = Config 165 | { configMaxTest :: Int 166 | , configMaxFail :: Int 167 | , configSize :: Int -> Int 168 | , configEvery :: Int -> [String] -> String 169 | } 170 | defaultConfig :: Config 171 | check :: Testable a => Config -> a -> IO () 172 | -- :: Config -> prop -> IO () 173 | 174 | -- property combinators 175 | forAll :: (Show a, Testable b) => Gen a -> (a -> b) -> 176 | Property 177 | -- :: Gen a -> (a -> prop) -> prop 178 | (==>) :: Testable a => Bool -> a -> Property 179 | -- :: Bool -> prop -> prop 180 | 181 | -- gathering test-case information 182 | label :: Testable a => String -> a -> Property 183 | -- :: String -> prop -> prop 184 | collect :: (Show a, Testable b) => a -> b -> Property 185 | -- :: Show a => a -> prop -> prop 186 | classify :: Testable a => Bool -> String -> a -> Property 187 | -- :: Bool -> String -> prop -> prop 188 | trivial :: Testable a => Bool -> a -> Property 189 | -- :: Bool -> prop -> prop 190 | 191 | -- generator combinators 192 | Gen -- :: * -> * ; Functor, Monad 193 | newtype Gen a 194 | = Gen (Int -> StdGen -> a) 195 | 196 | elements :: [a] -> Gen a 197 | two :: Gen a -> Gen (a,a) 198 | three :: Gen a -> Gen (a,a,a) 199 | four :: Gen a -> Gen (a,a,a,a) 200 | 201 | sized :: (Int -> Gen a) -> Gen a 202 | resize :: Int -> Gen a -> Gen a 203 | choose :: Random a => (a, a) -> Gen a 204 | oneof :: [Gen a] -> Gen a 205 | frequency :: [(Int, Gen a)] -> Gen a 206 | 207 | vector :: Arbitrary a => Int -> Gen [a] 208 | 209 | -- default generators 210 | Arbitrary(..) -- :: class 211 | class Arbitrary a where 212 | arbitrary :: Gen a 213 | coarbitrary :: a -> Gen b -> Gen b 214 | rand :: Gen StdGen 215 | promote :: (a -> Gen b) -> Gen (a -> b) 216 | variant :: Int -> Gen a -> Gen a 217 | 218 | -- testable 219 | Testable(..) -- :: class 220 | class Testable a where 221 | property :: a -> Property 222 | Property -- :: * 223 | newtype Property 224 | = Prop (Gen Result) 225 | 226 | -- For writing your own driver 227 | Result(..) -- :: data 228 | data Result 229 | = Result { ok :: Maybe Bool, 230 | stamp :: [String], 231 | arguments :: [String] } 232 | generate :: Int -> StdGen -> Gen a -> a 233 | evaluate :: Testable a => a -> Gen Result 234 | 235 | 2.2 porting policy for QuickCheck in Haskell to Ruby 236 | 237 | Because Test.QuickCheck is not small, I decide to divide 238 | implementation into several files. 239 | 240 | = testable.rb 241 | quickCheck :: Testable a => a -> IO () -- :: prop -> IO () 242 | verboseCheck :: Testable a => a -> IO () -- :: prop -> IO () 243 | test :: Testable a => a -> IO () -- :: prop -> IO () 244 | check :: Testable a => Config -> a -> IO () 245 | -- :: Config -> prop -> IO () 246 | (==>) :: Testable a => Bool -> a -> Property 247 | -- :: Bool -> prop -> prop 248 | -- gathering test-case information 249 | label :: Testable a => String -> a -> Property 250 | -- :: String -> prop -> prop 251 | collect :: (Show a, Testable b) => a -> b -> Property 252 | -- :: Show a => a -> prop -> prop 253 | classify :: Testable a => Bool -> String -> a -> Property 254 | -- :: Bool -> String -> prop -> prop 255 | trivial :: Testable a => Bool -> a -> Property 256 | -- :: Bool -> prop -> prop 257 | -- testable 258 | Testable(..) -- :: class 259 | class Testable a where 260 | property :: a -> Property 261 | 262 | evaluate :: Testable a => a -> Gen Result 263 | 264 | = config.rb 265 | Config(..) -- :: * 266 | data Config = Config 267 | { configMaxTest :: Int 268 | , configMaxFail :: Int 269 | , configSize :: Int -> Int 270 | , configEvery :: Int -> [String] -> String 271 | } 272 | defaultConfig :: Config 273 | 274 | = gen.rb 275 | -- property combinators 276 | forAll :: (Show a, Testable b) => Gen a -> (a -> b) -> 277 | Property 278 | -- :: Gen a -> (a -> prop) -> prop 279 | -- generator combinators 280 | Gen -- :: * -> * ; Functor, Monad 281 | newtype Gen a 282 | = Gen (Int -> StdGen -> a) 283 | elements :: [a] -> Gen a 284 | two :: Gen a -> Gen (a,a) 285 | three :: Gen a -> Gen (a,a,a) 286 | four :: Gen a -> Gen (a,a,a,a) 287 | 288 | sized :: (Int -> Gen a) -> Gen a 289 | resize :: Int -> Gen a -> Gen a 290 | choose :: Random a => (a, a) -> Gen a 291 | oneof :: [Gen a] -> Gen a 292 | frequency :: [(Int, Gen a)] -> Gen a 293 | 294 | vector :: Arbitrary a => Int -> Gen [a] 295 | 296 | rand :: Gen StdGen 297 | promote :: (a -> Gen b) -> Gen (a -> b) 298 | variant :: Int -> Gen a -> Gen a 299 | 300 | generate :: Int -> StdGen -> Gen a -> a 301 | 302 | = arbitrary.rb 303 | Arbitrary(..) -- :: class 304 | class Arbitrary a where 305 | arbitrary :: Gen a 306 | coarbitrary :: a -> Gen b -> Gen b 307 | 308 | = property.rb 309 | Property -- :: * 310 | newtype Property 311 | = Prop (Gen Result) 312 | 313 | = result.rb 314 | Result(..) -- :: data 315 | data Result 316 | = Result { ok :: Maybe Bool, 317 | stamp :: [String], 318 | arguments :: [String] } 319 | 320 | 2.2.1 testable.rb 321 | 322 | -- testable 323 | Testable(..) -- :: class 324 | class Testable a where 325 | property :: a -> Property 326 | type class Testable is translated into a module Testable. 327 | 328 | Haskell 329 | quickCheck :: Testable a => a -> IO () -- :: prop -> IO () 330 | verboseCheck :: Testable a => a -> IO () -- :: prop -> IO () 331 | test :: Testable a => a -> IO () -- :: prop -> IO () 332 | check :: Testable a => Config -> a -> IO () 333 | -- :: Config -> prop -> IO () 334 | ---> 335 | Ruby 336 | Testable#quick_check :: [nil, [TrueClass, FalseClass]] 337 | Testable#verbose_check :: [nil, [TrueClass, FalseClass]] 338 | Testable#test :: [nil, [TrueClass, FalseClass]] 339 | Testable#check :: [Config, [TrueClass, FalseClass]] 340 | 341 | For using checking functions with unit testing framework, they 342 | should return the result as a boolean value. 343 | 344 | Haskell 345 | (==>) :: Testable a => Bool -> a -> Property 346 | -- :: Bool -> prop -> prop 347 | ---> 348 | Ruby 349 | Testable#imply :: [] 350 | method imply takes a block for the boolean property. 351 | 352 | Haskell 353 | -- gathering test-case information 354 | label :: Testable a => String -> a -> Property 355 | -- :: String -> prop -> prop 356 | collect :: (Show a, Testable b) => a -> b -> Property 357 | -- :: Show a => a -> prop -> prop 358 | classify :: Testable a => Bool -> String -> a -> Property 359 | -- :: Bool -> String -> prop -> prop 360 | trivial :: Testable a => Bool -> a -> Property 361 | -- :: Bool -> prop -> prop 362 | ---> 363 | Ruby 364 | Testable#label :: [Object, Property] 365 | alias Testable#collect 366 | Testable#classify :: [Object, Property] # Object -> String 367 | Testable#trivial :: [nil, Property] 368 | 369 | In ruby, label and collect can be regarded as same with 'to_s'. 370 | classify and trivial take a block for boolean condition. 371 | classify calls label internally. 372 | 373 | Haskell 374 | evaluate :: Testable a => a -> Gen Result 375 | where property :: Testable a => a -> Property 376 | Property = Prop (Gen Result) 377 | ---> 378 | Ruby 379 | nop 380 | Because the name evaluate is similar to eval in Ruby, I decide to 381 | not implement evaluate. evaluate is almost same to property. 382 | 383 | 2.2.2 config.rb 384 | 385 | Haskell 386 | Config(..) -- :: * 387 | data Config = Config 388 | { configMaxTest :: Int 389 | , configMaxFail :: Int 390 | , configSize :: Int -> Int 391 | , configEvery :: Int -> [String] -> String 392 | } 393 | defaultConfig :: Config 394 | ---> 395 | Ruby 396 | class Config 397 | Config#initialize :: [[Integer, Integer, Proc, Proc], ...] 398 | 399 | There is two ways to implement a method with functions. 400 | Because the initialize takes two functions, I choose to implement 401 | them as Proc objects and as not block (it is confused to use both 402 | Proc and block) 403 | 404 | 2.2.3 gen.rb 405 | 406 | gen.rb is not a small file to implement almost all of features in 407 | QuickCheck. 408 | 409 | Haskell 410 | -- generator combinators 411 | Gen -- :: * -> * ; Functor, Monad 412 | newtype Gen a 413 | = Gen (Int -> StdGen -> a) 414 | ---> 415 | Ruby 416 | class Gen 417 | Gen#initialize [nil, ...] 418 | initialize takes a block {|n, r| } where n is an integer and r is a 419 | random generator. 420 | 421 | Haskell 422 | -- property combinators 423 | forAll :: (Show a, Testable b) => Gen a -> (a -> b) -> 424 | Property 425 | -- :: Gen a -> (a -> prop) -> prop 426 | ---> 427 | Ruby 428 | Gen#forall :: [nil, Property] 429 | forall takes a block. 430 | 431 | Haskell 432 | elements :: [a] -> Gen a 433 | ---> 434 | Ruby 435 | Gen.elements :: [[Object], Gen] 436 | 437 | Haskell 438 | two :: Gen a -> Gen (a,a) 439 | three :: Gen a -> Gen (a,a,a) 440 | four :: Gen a -> Gen (a,a,a,a) 441 | ---> 442 | Ruby 443 | nop 444 | Because Ruby does not have tuple (but has Array). I wonder whether 445 | the monadic functions two, three and four in Ruby. So I deferred to 446 | implement them until I need them. 447 | 448 | Haskell 449 | sized :: (Int -> Gen a) -> Gen a 450 | resize :: Int -> Gen a -> Gen a 451 | choose :: Random a => (a, a) -> Gen a 452 | oneof :: [Gen a] -> Gen a 453 | frequency :: [(Int, Gen a)] -> Gen a 454 | vector :: Arbitrary a => Int -> Gen [a] 455 | rand :: Gen StdGen 456 | promote :: (a -> Gen b) -> Gen (a -> b) 457 | variant :: Int -> Gen a -> Gen a 458 | generate :: Int -> StdGen -> Gen a -> a 459 | ---> 460 | Ruby 461 | Gen#sized :: [nil, Gen], takes a block 462 | Gen#resize :: [Integer, Gen] 463 | 464 | Gen.choose :: [[lo::?, hi::?], Gen] 465 | where lo and hi belongs Random 466 | Gen.oneof :: [[Gen], Gen] 467 | Gen.frequency :: [[Integer, Gen], Gen] 468 | Gen.vector :: [Integer, Gen] 469 | Gen.rand :: [nil, Gen] 470 | Gen.promote :: [nil, Gen], takes a block 471 | 472 | Gen#variant :: [Integer, Gen] 473 | Gen#generate :: [[Integer, StdGen], ?] 474 | 475 | 2.2.3 arbitrary.rb 476 | 477 | Haskell 478 | Arbitrary(..) -- :: class 479 | class Arbitrary a where 480 | arbitrary :: Gen a 481 | coarbitrary :: a -> Gen b -> Gen b 482 | ---> 483 | Ruby 484 | module Arbitrary 485 | module Coarbitrary 486 | 487 | Because arbitrary should be implemented as a class method, while 488 | coarbitrary as a instance method, they are included in different 489 | modules. 490 | 491 | 2.2.4 property.rb 492 | 493 | Haskell 494 | Property -- :: * 495 | newtype Property 496 | = Prop (Gen Result) 497 | ---> 498 | Ruby 499 | class Property 500 | 501 | There are several ways to implement Property in Ruby: 502 | - as a class independently <-- I choose 503 | - as a subclass of Gen 504 | 505 | At first implementation (experimentally), I tried to write Property 506 | as a subclass of Gen. However, initialization is different and debug 507 | is imprecated. 508 | 509 | 2.2.5 result.rb 510 | 511 | Haskell 512 | Result(..) -- :: data 513 | data Result 514 | = Result { ok :: Maybe Bool, 515 | stamp :: [String], 516 | arguments :: [String] } 517 | ---> 518 | Ruby 519 | class Result 520 | attr_reader :ok, :stamp, :arguments 521 | 522 | 3. Test.QuickCheck.Batch 523 | 524 | -- | Run the test. 525 | run :: Testable a => a -> TestOptions -> IO TestResult 526 | -- | Prints a one line summary of various tests with common theme 527 | runTests :: String -> TestOptions -> [TestOptions -> IO TestResult] -> IO () 528 | defOpt :: TestOptions 529 | data TestOptions = TestOptions { 530 | no_of_tests :: Int, -- ^ number of tests to run. 531 | length_of_tests :: Int, -- ^ time limit for test, in seconds. 532 | debug_tests :: Bool } 533 | data TestResult = TestOk String Int [[String]] 534 | | TestExausted String Int [[String]] 535 | | TestFailed [String] Int 536 | | TestAborted Exception 537 | isBottom -- :: a -> Bool 538 | bottom -- :: a {- _|_ -} 539 | 540 | private functions 541 | tests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] 542 | -> IO TestResult 543 | 544 | 3.1 porting policy for QuickCheck.Batch in Haskell to Ruby 545 | 546 | = testoptions.rb 547 | defOpt :: TestOptions 548 | data TestOptions = TestOptions { 549 | no_of_tests :: Int, -- ^ number of tests to run. 550 | length_of_tests :: Int, -- ^ time limit for test, in seconds. 551 | debug_tests :: Bool } 552 | 553 | data TestResult = 554 | | TestOk String Int [[String]] -- message ntest stamps 555 | | TestExausted String Int [[String]] -- message ntest stamps 556 | | TestFailed [String] Int -- results ntest 557 | | TestAborted Exception -- exception 558 | 559 | TestOk ---> class TestOk 560 | TestExausted ---> class TestExausted 561 | TestFailed ---> class TestFailed 562 | TestAborted ---> raise 563 | 564 | = testable.rb 565 | run :: Testable a => a -> TestOptions -> IO TestResult 566 | runTests :: String -> TestOptions -> [TestOptions -> IO TestResult] -> IO () 567 | 568 | isBottom and bottom have not been implemented in Ruby yet 569 | 570 | = config.rb 571 | tests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] 572 | -> IO TestResult 573 | 574 | 3.1.1 testresult.rb 575 | 576 | Haskell 577 | data TestResult = 578 | | TestOk String Int [[String]] -- message ntest stamps 579 | | TestExausted String Int [[String]] -- message ntest stamps 580 | | TestFailed [String] Int -- results ntest 581 | | TestAborted Exception -- exception 582 | --> 583 | Ruby 584 | class TestResult 585 | attr_accessor :message, :ntest, :stamps 586 | end 587 | class TestOk < TestResult; end; 588 | class TestExausted < TestResult; end; 589 | class TestFailed; end; 590 | exception is raised in runtime 591 | 592 | 3.1.2 testoptions.rb 593 | 594 | Haskell 595 | defOpt :: TestOptions 596 | data TestOptions = TestOptions { 597 | no_of_tests :: Int, -- ^ number of tests to run. 598 | length_of_tests :: Int, -- ^ time limit for test, in seconds. 599 | debug_tests :: Bool } 600 | ---> 601 | Ruby 602 | class TestOptions 603 | TestOptions#initialize(ntests, len, debug=nil) 604 | 605 | defOpt can be implemented as the default varues of initialize 606 | 607 | 3.1.3 testable.rb 608 | 609 | Haskell 610 | run :: Testable a => a -> TestOptions -> IO TestResult 611 | runTests :: String -> TestOptions -> [TestOptions -> IO TestResult] -> IO () 612 | ---> 613 | Ruby 614 | Testable#run :: [[TestOptions], Result] 615 | Testable#run_tests(mes, opts, tests) 616 | :: [[String, TestOptions. [Proc]], [Bool]] 617 | should return results 618 | 619 | 3.1.4 config.rb 620 | 621 | Haskell 622 | tests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] 623 | -> IO TestResult 624 | ---> 625 | Ruby 626 | RushCheckConfig#batch(n, v) 627 | RushCheckConfig#test_batch 628 | -------------------------------------------------------------------------------- /data/doc/rushcheck.thtml: -------------------------------------------------------------------------------- 1 |
2 | last-modified: 2006-10-13 Daisuke IKEGAMI 3 |
4 | 5 | This tutorial is written in a simple markup language 6 | "RedCloth3":http://whytheluckystiff.net/ruby/redcloth/. 7 | This file is also dawnloadable from "rushcheck.txt":rushcheck.txt 8 | and also in the distribution. 9 | 10 | h1. Index 11 | 12 | # Getting start 13 | ## (Option 1.) install by RubyGems 14 | ## (Option 2.) using setup.rb after download source codes 15 | ## darcs repository 16 | # Tutorial for writing testcase 17 | ## At first, we have to require the library. 18 | ## Start writing testcases 19 | ## Watching the statistics 20 | ### 'trivial' 21 | ### 'classify' 22 | # Combining unit testing and RushCheck 23 | # Combining RSpec and RushCheck 24 | ## With another basic classes for assertions 25 | ### SpecialString 26 | ### Array and RandomArray 27 | ### Hash and RandomHash 28 | ### Proc and RandomProc 29 | ## How to define random generators for user defined class 30 | ### using Gen.create 31 | ### using Gen.bind and Gen.unit 32 | ### using Gen.new 33 | ## Another staffs in Gen class 34 | ### Gen.choose 35 | ### Gen.frequency 36 | ### Gen.lift_array 37 | ### Gen.oneof 38 | ### Gen.promote 39 | ### Gen.rand 40 | ### Gen.sized 41 | ### Gen.unit 42 | ### Gen.vector 43 | ## how to write random Proc which returns objects in YourClass 44 | # Further information 45 | 46 | h1. Getting start 47 | 48 | There are two ways to install RushCheck. 49 | 50 | h2. (Option 1.) install by RubyGems 51 | 52 | You can install RushCheck easily with 53 | "rubygems":http://docs.rubygems.org/. 54 | 55 |
% sudo gem install rushcheck
56 | 57 | Done! 58 | 59 | h2. (Option 2.) using setup.rb after download source codes. 60 | 61 | Instead, if you don't have rubygems, or like to install from source 62 | codes, then you can follow the steps. 63 | 64 | # Get the tar-ball of current release from 65 | "download page":http://rubyforge.org/frs/?group_id=2027 66 | # Expand it 67 | # finally, type as follows 68 | 69 |
% sudo ruby setup.rb
70 | 71 | See also the following; 72 | 73 |
% setup.rb help
74 | 75 | h2. (Developer's another option)darcs repository 76 | 77 | Our darcs repository is provided for developer RushCheck itself, and skip this 78 | section if you want to only use RushCheck! 79 | If you have an interest to our development version (not yet shipped), then try to access our darcs repository. 80 | darcs is a version control system and can be easily installed from 81 | "the darcs page":http://abridgegame.org/darcs/. After installing 82 | darcs, you can get the current development of RushCheck by following: 83 | 84 |
 85 | % darcs get --partial http://rushcheck.rubyforge.org/repos/rushcheck
 86 | 
87 | 88 | After darcs getting, you can get the newest files whenever you want to 89 | type 90 |
 91 | % darcs pull -a
 92 | 
93 | 94 | h1. Tutorial for writing testcase 95 | 96 | h2. At first, we have to require the library. 97 | 98 | If you have installed RushCheck using RubyGems, then you should add 99 | the following two lines to your test codes. 100 | 101 |
102 | require 'rubygems'
103 | require_gem 'rushcheck'
104 | 
105 | 106 | Otherwise, if you have installed RushCheck by setup.rb, then 107 | add the simple one line. 108 | 109 |
110 | require 'rushcheck'
111 | 
112 | 113 | The following maybe useful if you don't matter whether you use 114 | rubygems or not. 115 |
116 | begin
117 |   require 'rubygems'
118 |   require_gem 'rushcheck'
119 | rescue LoadError
120 |   require 'rushcheck'
121 | end
122 | 
123 | 124 | h2. Don't forget to require also your library file if the class you want to test is included in it. 125 | 126 |
127 | require 'your_library'
128 | 
129 | 130 | h2. Start writing testcases 131 | 132 | OK, then we can start to write test codes. RushCheck requires to 133 | write __assertion based__ testcases. An assertion of function (or 134 | method) consists of triple where inputs, guard conditions and a 135 | testing property block. Here is a templete of assertion: 136 | 137 |
138 | RushCheck::Assertion.new(Class1, Class2, ...) do |var1, var2, ...|
139 |   # testing property block
140 |   # this block should return boolean value (true, false or nil)
141 |   # in Ruby
142 | end
143 | 
144 | 145 | For example, assume that we want to test the method 'sort' in 146 | Array. The following assertion is a simple testcase: 147 | 148 |
149 | ast_zero = 
150 |   RushCheck::Assertion.new() do 
151 |     [].sort == []
152 |   end
153 | 
154 | 155 | whether if we sort empty array the result is also empty. 156 | This assertion does not have any inputs and guards but only have 157 | the property block. 158 | 159 | Let's see another example: 160 |
161 | ast_one = 
162 |   RushCheck::Assertion.new(Integer) do |x|
163 |     [x].sort == [x]
164 |   end
165 | 
166 | 167 | This assertion defines that we claim for any integer 'x', an array 168 | [x] is not changed after sorting. We can test the property 100 times: 169 | 170 |
171 | irb> ast_one.verbose_check
172 | 1:
173 | [-1]
174 | 2:
175 | [-2]
176 | ... (snip) ...
177 | 99:
178 | [6]
179 | 100:
180 | [1]
181 | OK, passed 100 tests.
182 | true
183 | irb> ast_one.check
184 | OK, passed 100 tests.
185 | true
186 | irb>
187 | 
188 | 189 | RushCheck supports random testing such as above. We will see later 190 | how to change the number of test, how to change the distribution, 191 | etc. Here we learned two testing methods, verbose_check and check. 192 | 'verbose_check' displays every instances of test, on the other hand 193 | 'check' does not display inputs but only the result. 194 | 195 | Next example shows how RushCheck displays the counter example. If 196 | an assertion is failed, then there is a counter example of the 197 | assertion. 198 | 199 |
200 | ast_two = 
201 |   RushCheck::Assertion.new(Integer, Integer) do |x, y|
202 |     [x, y].sort == [x, y]
203 |   end
204 | 
205 | 206 | The above test is failed sometimes and we can find the result after 207 | checking. 208 | 209 |
210 | irb> ast_two.check
211 | Falsifiable, after 3 tests:
212 | [2, -1]
213 | false
214 | irb>
215 | 
216 | 217 | Here, the counter example [2, -1] of the assertion is appeared. In 218 | fact, [2, -1].sort equals [-1, 2] and does not equal [2, -1]. 219 | 220 | Sometimes, we need several pre-conditions for tests. For example, 221 | if we have a pre-condition 'x <= y' in the previous assertion, then 222 | the assertion should be succeeded. We can write pre-conditions as 223 | guards in the property block: 224 | 225 |
226 | ast_two_sorted =
227 |   RushCheck::Assertion.new(Integer, Integer) do |x, y|
228 |     RushCheck::guard { x <= y }
229 |     [x, y].sort == [x, y]
230 |   end
231 | 
232 |
233 | irb> ast_two_sorted.check
234 | OK, passed 100 tests.
235 | true
236 | irb>
237 | 
238 | 239 | Note that it is always assumed that the number of arguments of 240 | Assertion.new must be equal to the number of variables of the block. 241 | (Until ver 0.3, experimentally the number of arguments can be 242 | differed, however from ver 0.4, they must be equal.) 243 | The arguments of Assertion.new corresponds to the variables of block 244 | one to one. We can have any number of guards in the property block. 245 | If the guard property does not hold in random testing, then the test 246 | is abort and try to take another random instances. We can imagine the 247 | following test sequences in ast_two_sorted. 248 | 249 | # x, y = 1, 2 250 | -> guard g is passed 251 | -> check the test block and succeed 252 | # x, y = 3, 1 253 | -> guard g is failed and abort 254 | (not counted) 255 | # x, y = 2, 3 256 | -> ... 257 | # ... (whether passed 100 tests or not) 258 | 259 | h2. Watching the statistics 260 | 261 | In the previous section, we saw two methods 'check' and 262 | 'verbose_check'. Sometimes, we want to watch the statistics of 263 | random testing. However 'check' shows less information, and 264 | 'verbose_check' is too noisy. RushCheck has several options to 265 | watch the statistics. 266 | 267 | h3. 'trivial' 268 | 269 | You may not want to check the trivial case in random test. As we 270 | have seen, we can ignore the trivial case using the guard 271 | condition. However, on the other hand, we can watch how many 272 | trivial cases are appeared in random test. 273 | 274 |
275 | ast_sort_triv =
276 |   RushCheck::Assertion.new(Integer, Integer) do |x, y|
277 |     RushCheck::guard {x <= y}
278 |     ([x, y].sort == [x, y]).trivial{ x == y }
279 |   end
280 | 
281 |
282 | irb> ast_sort_triv.check
283 | OK, passed 100 tests(14%, trivial).
284 | true
285 | 
286 | 287 | Here, we have 14% (i.e. 14 times) trivial (x == y) cases in the 288 | test. 289 | 290 | h3. 'classify' 291 | 292 | In addition, we can give another names to watching statistics. 293 | 294 |
295 | ast_sort_classify =
296 |   RushCheck::Assertion.new(Integer, Integer) do |x, y|
297 |     RushCheck::guard {x <= y}
298 |     test = ([x, y].sort == [x, y])
299 |     test.classify('same'){ x == y }.
300 |       classify('bit diff'){ (x - y).abs == 1 } 
301 |   end
302 | 
303 |
304 | irb> ast_sort_classify.check
305 | OK, passed 100 tests.
306 | 18%, same.
307 | 11%, bit diff.
308 | true
309 | irb>
310 | 
311 | 312 | h2. Combining unit testing and RushCheck 313 | 314 | The library 'test/unit' of Ruby is useful for unit testing. Here is a trick to use 'test/unit'. 315 | 316 |
317 |   def forall(*cs, &f)
318 |     assert(RushCheck::Claim.new(*cs, &f).check)
319 |   end
320 | 
321 | 322 | The class Claim is a subclass of Assertion. The meaning is almost similar to Assertion, however 323 | Claim does not check the result value of the given block 'f'. Because assertions in 'test/unit' 324 | such as 'assert_equal' does not return any result, but return nil, we don't need to check the 325 | result values of a sequence of assertions. Nevertheless we can check the testcase because 326 | the assertions in 'test/unit' raises an exception if the testcase is failed. 327 | 328 | Example: 329 |
330 |   def test_not_empty_after_push
331 |     array = Array.new
332 |     forall(Integer) do |item|
333 |       array.push item
334 |       assert(! array.empty?)
335 |     end
336 |   end
337 | 
338 | 339 | h2. Combining RSpec and RushCheck 340 | 341 | "RSpec":http://rspec.rubyforge.org is another testing framework which helps Behaviour Driven Development (BDD). 342 | To combine RSpec and RushCheck, the following trick maybe useful and easy to read: 343 |
344 |   def forall(*cs, &f)
345 |     RushCheck::Claim.new(*cs, &f).check.should_equal true
346 |   end
347 | 
348 | 349 | Then, for example, we can write a specification of Array#push as follows: 350 |
351 | context "An empty array" do
352 | 
353 |   specify "should not be empty after 'push'" do
354 |     forall(Integer) do |item|
355 |       array = Array.new
356 |       array.push item
357 |       array.should_not_be_empty
358 |     end
359 |   end
360 | 
361 | end
362 | 
363 | 364 | See also 'examples/rspec' in the distribution of RushCheck for details. 365 | 366 | h2. With another basic classes for assertions 367 | 368 | In previous sections, we have seen how to check assertions for any 369 | integers. In similar way, we can define the assertions for any float 370 | numbers or any string. 371 | 372 |
373 | RushCheck::Assertion.new(Float, String, ...) do |ratio, name,...|
374 |   # testcase
375 | end
376 | 
377 | 378 | RushCheck has default random generators for the following basic classes: 379 | 380 | * Integer 381 | * Float 382 | * String 383 | 384 | If you want to change the distribution of randomness, then you have 385 | to write a code for generator. There are some examples for writing 386 | generators. In the next section, I will introduce SpecialString 387 | whose distribution differs to the default implementation of String. 388 | 389 | Even Array, Hash and Proc are also primitive classes, however the 390 | randomness of them should be changed in testing codes. Therefore, 391 | RushCheck provides an abstract generators for them. Programmer need 392 | to write a subclass of the abstract generators. Later 393 | I will show you how to write a subclass of RandomArray, etc. 394 | 395 | h3. SpecialString 396 | 397 | Sometimes, we want to check special characters, for example the 398 | backslash '\' or unprinted characters 'ESC', 'NUL' and so on. 399 | SpecialString is a subclass of String which is defined in 400 | 'rushcheck/string'. This library is already required in 401 | 'rushcheck/rushcheck' and don't need to require again. 402 | 403 | The output of random generator of SpecialString has different 404 | distribution of its of String. At String, the distribution of 405 | characters are normalized. On the other hand, the distribution in 406 | SpecialString is weighted and the generator provides special 407 | characters more often than alphabets. In detail, the distribution is 408 | as follows: 409 | 410 | |_.the distribution of SpecialString | 411 | |Alphabet |15% | 412 | |Control characters|50% (such as NUL)| 413 | |Number |10% | 414 | |Special characters|25% (such as '\')| 415 | 416 | Using SpecialString in assertions, it is more likely to find the 417 | counter example. The following example is the famous bug which is 418 | called 'malformed format bug'. 419 | 420 |
421 | malformed_format_string =
422 |   RushCheck::Assertion.new(String) { |s| sprintf(s); true}
423 | 
424 | malformed_format_string2 =
425 |   RushCheck::Assertion.new(SpecialString) { |s| sprintf(s); true}
426 | 
427 |
428 | irb> malformed_format_string.check
429 | Falsifiable, after 86 tests:
430 | Unexpected exception: #
431 |     ... snip error traces ...
432 | ["\n&'e!]hr(%&\031Vi\003 }ss"]
433 | false
434 | irb> malformed_format_string2.check
435 | Falsifiable, after 15 tests:
436 | Unexpected exception: #
437 | ["%\037-R"]
438 | false
439 | 
440 | 441 | In these results, we can see RushCheck find the counter example 442 | after 86 tests with String, on the other hand, find it quickly after 15 443 | tests with SpecialString. 444 | 445 | It is easy to change the distribution in SpecialString. You can 446 | define your subclass of SpecialString as follows: 447 | 448 |
449 | class YourSpecialString < SpecialString
450 |   @@frequency = { 'alphabet' => 1,
451 |                   'control'  => 0,
452 |                   'number'   => 0,
453 |                   'special'  => 9 }  
454 | end
455 | 
456 | 457 | h3. Array and RandomArray 458 | 459 | The meaning of randomness for Array must be changed in testing 460 | codes. Sometimes you needs a random array of Integer, and another a 461 | random array of String. So what is random array? 462 | 463 | RushCheck provides an abstract class RandomArray for abstract random 464 | generator. Programmer have to write a subclass of RandomArray as 465 | follows: 466 | 467 |
468 | # for random array of integers
469 | class MyRandomArray < RandomArray; end
470 | MyRandomArray.set_pattern(Integer) {|ary, i| Integer}
471 | 
472 | 473 | The class method set_pattern takes a variable and a block. 474 | Because array is __inductive__ structure, it can be defined by the 475 | basecase and the inductive step. 476 | 477 | For example, let's consider a random array in the following pattern 478 | @[Integer, String, Integer, String, ...]@ 479 | where it has a random Integer at the odd index and a random String at 480 | the even index. Then we can write a random array with this pattern: 481 | 482 |
483 | MyRandomArray.set_pattern(Integer) do |ary, i|
484 |   if i % 2 == 0
485 |   then Integer
486 |   else String
487 |   end
488 | end
489 | 
490 | 491 | More complecated example? OK, let's consider a stream: 492 | * the first component is Integer 493 | * if the i-th component is positive integer 494 | ** then (i+1)-th component is String 495 | ** otherwise the (i+1)-th component is Integer 496 | 497 |
498 | MyRandomArray.set_pattern(Integer) do |ary, i|
499 |   if ary[i].kind_of?(Integer) && ary[i] >= 0
500 |   then String
501 |   else Integer
502 |   end
503 | end 
504 | 
505 | 506 | In this way, we can define any random array with any pattern. 507 | 508 | h3. Hash and RandomHash 509 | 510 | Hash is also primitive and not so clear what is a random hash, like 511 | array. RushCheck provides an abstract random generator RandomHash, 512 | and programmer can write a subclass of RandomHash. 513 | 514 |
515 | class MyRandomHash < RandomHash; end
516 | pat = { 'key1' => Integer, 'key2' => String }
517 | MyRandomHash.set_pattern(pat)
518 | 
519 | 520 | In this example, we can get a random Hash with two keys ('key1' and 521 | 'key2'). Here the keys are String, but we can give any object as 522 | usual in Hash. Is it clear to define random hash? I think so. 523 | (If not it is clear, then the interface may be changed in future) 524 | 525 | h3. Proc and RandomProc 526 | 527 | It is not difficult to create a random Proc object. 528 | As we saw in the previous sections, we have to write a subclass of 529 | RandomProc. 530 | 531 |
532 | class MyRandomProc < RandomProc; end
533 | MyRandomProc.set_pattern([Integer], [Integer])
534 | 
535 | 536 | Here, we define a random procedure which takes an integer and 537 | returns an integer also. In general, Ruby's function and method can 538 | be regarded as a relation. (not a function in mathematics!) 539 | Therefore I think random procedures can be generated by a pair of 540 | inputs and outputs. 541 | 542 | Let's consider a simple example. In general, any functions f, g, and 543 | h should satisfy the associativity property: 544 |
545 |   for all x, f(g(h(x)) === (f . g)(h (x))
546 |      where f . g is a composition of functions in mathematical way
547 | 
548 | 549 |
550 | class Proc
551 |   # application
552 |   def **(other)
553 |     Proc.new do |*args|
554 |       res = other.call(*args)
555 |       call(*res)
556 |     end
557 |   end
558 | end
559 | 
560 | class MyRandomProc < RandomProc; end
561 | 
562 | def associativity_integer
563 |   MyRandomProc.set_pattern([Integer], [Integer])
564 |   RushCheck::Assertion.new(MyRandomProc, MyRandomProc, 
565 |                            MyRandomProc, Integer) do
566 |     |f, g, h, x|
567 |     (f ** (g ** h)).call(x) == ((f ** g) ** h).call(x)
568 |   end.check
569 | end
570 | 
571 | 572 | P.S. 573 | The arbitrary method is used to create a random object for test 574 | instance. Then you may wonder what is the coarbitrary method? 575 | The coarbitrary method is used to generate a random procedure 576 | (lambda), which is one of central idea in QuickCheck. 577 | 578 | h2. How to define random generators for user defined class 579 | 580 | h3. Understand your class. What is a random object? 581 | 582 | To use your class in the assertion, like follows 583 |
584 | RushCheck::Assertion.new(YourClass) { |obj, ...| ...}
585 | 
586 | you have to write a code which generates a random object in your 587 | class. Therefore, at first we have to consider 588 | __what is a random object in YourClass?__ 589 | 590 | Sometimes the answer is not unique; there may be several ways for 591 | generating random objects. Like SpecialString in RushCheck, you can 592 | also write an abstract random generator. 593 | 594 | OK, after thinking about the question, we have to write a code. 595 | To define random generators, we have to add a class method arbitrary 596 | in YourClass. 597 | 598 |
599 | class YourClass
600 |   extend RushCheck::Arbitrary
601 | 
602 |   def self.arbitrary
603 |     # override the class method arbitrary 
604 |     ...
605 |   end
606 | end
607 | 
608 | 609 | If you need to define a random proc which returns a object in 610 | YourClass, you have to include Coarbitrary, also. 611 | 612 |
613 | class YourClass
614 |   extend RushCheck::Arbitrary
615 |   include RushCheck::Coarbitrary
616 | 
617 |   def self.arbitrary
618 |     ...
619 |   end
620 | 
621 |   def coarbitrary(g)
622 |     ...
623 |   end
624 | end
625 | 
626 | 627 | However, because it is little complecated to implement both 628 | arbitrary and coarbitrary, let's focus how to implement arbitrary 629 | first. 630 | 631 | Let's consider the first example Candy. The Candy class requires its 632 | name and its price at initialize. The name should be a String, and the 633 | price Integer. The Candy class may have several instance methods, but 634 | they are omitted because not important to define the random object. 635 | 636 |
637 | class Candy
638 | 
639 |   def initialize(name, price)
640 |     raise unless price >= 0
641 |     @name, @price = name, price
642 |   end
643 | 
644 |   def foo
645 |     ...
646 |   end
647 | 
648 |   def bar
649 |     ...
650 |   end
651 | 
652 | end
653 | 
654 | 655 | To write random generator, we have to look up 'initialize'. 656 | Here, assume that @name belongs String and @price belongs Integer. 657 | It is natural that we assumes @price should be positive. 658 | 659 | One simple random generator for Candy: 660 | 661 | h3. (1) using Gen.create 662 | 663 |
664 | class Candy
665 |   extend RushCheck::Arbitrary
666 | 
667 |   def self.arbitrary
668 |     RushCheck::Gen.create(String, Integer) do |name, price|
669 |       RushCheck::guard { price >= 0 }
670 |       new(name, price)
671 |     end
672 |   end
673 | end
674 | 
675 | 676 | Gen.create takes an array of Gen object (here, [Integer.arbitrary, 677 | String.arbitrary]) and a block. The block takes variables which is 678 | corresponded to the array, as Assertion.new. If guard is failed, 679 | then RushCheck retry to create another random instance. 680 | 681 | Note that we can use a trick instead of the guard property. 682 |
683 |   price = - price if price < 0   # g.guard { price >= 0 }
684 | 
685 | In this case, this is more efficient to generate random instance 686 | than with the guard. However, sometimes we don't have this kind 687 | trick and we can use some guards. 688 | 689 | Remark: from version 0.4, Gen.create is changed to require classes 690 | in its argument from Gen objects. 691 | 692 | h3. (2) using Gen#bind and Gen.unit 693 | 694 |
695 | class Candy
696 |   extend RushCheck::Arbitrary
697 | 
698 |   def self.arbitrary
699 |     String.arbitrary.bind do |name|
700 |       Integer.arbitrary.bind do |price|
701 |         price = - price if price < 0   # trick as I described above
702 |         RushCheck::Gen.unit(new(name, price))
703 |       end
704 |     end
705 |   end
706 | end
707 | 
708 | 709 | Puzzled? OK, they can be readed as follows: 710 | 711 | * take a random (arbitrary) string and call it 'name' 712 | ** take a random integer and call it 'price' 713 | *** return a Gen object which has a new Candy(name, price) 714 | 715 | In general, the class method arbitrary should return a Gen object. 716 | Check also gen.rb in RushCheck. There are several combinators to 717 | create random generators. 718 | 719 | There are several way to implement random generators. The next 720 | example is to use Gen.new without bind and unit. 721 | 722 | h3. (3) using Gen.new 723 | 724 |
725 | class Candy
726 |   extend RushCheck::Arbitrary
727 | 
728 |   def self.arbitrary
729 |     RushCheck::Gen.new do |n, r|
730 |       r2 = r
731 |       name, price = [String, Integer].map do |c|
732 |         r1, r2 = r2.split
733 |         c.arbitrary.value(n. r1)
734 |       end
735 |     price = - price if price < 0 # trick
736 |       new(name, price)
737 |     end
738 |   end
739 | end
740 | 
741 | 742 | This pattern is useful if your class has many valiables for 743 | initialize. Because binding patterns needs much depth of method call 744 | chains, it may be failed. This technique is used to avoid the stack 745 | overflow. This is one difference between Haskell and Ruby. 746 | 747 | The implementation can be understanded as follows: 748 | * self.arbitrary returns a new Gen object. 749 | ** let n be an integer and r be a random generator. 750 | ** let r2 equal r 751 | ** name and price are both random objects where 752 | *** get new two random generator r1 and r2 from old r2 753 | *** assign a random value by generating 'arbitrary.value(n, r1)' 754 | **** and discard the random generator r1 755 | **** ... 756 | 757 | Note that we need new random generator for each random object. 758 | Here we create new random generator by spliting. If you use same 759 | random generator for generating different objects, then the 760 | distribution of objects are same (not generated randomly). 761 | 762 | Because sometimes the initialize of YourClass is complecated, 763 | then the random generator self.arbitrary turns to be complicated 764 | also. 765 | 766 | In next sections, I will introduce several generators in gen.rb. 767 | They may be useful to create your own random genrator. See also rdoc 768 | of Gen. 769 | 770 | h3. Appendix: expensive candy. 771 | 772 | Using Gen.create to generate random instance, if we gives another 773 | guard such as 774 |
775 |     RushCheck::guard { price >= 100000 }  # very expensive candy!
776 | 
777 | then it consumes much time to generate random instance because the 778 | guard fails so many times. When RushCheck creates random instances, 779 | it starts smallest as possible, then the size becomes larger in 780 | repeating tests. Therefore, if the guard instance seems failed so 781 | often, then we need another seeds of generators. Here is another 782 | generators for expensive candy instance. 783 | 784 |
785 | lo = 100000
786 | g = RushCheck::Gen.sized { |n| RushCheck::Gen.choose(lo, n + lo)}
787 | xs = [String.arbitrary, g]
788 | 
789 | See gen.rb and the following sections in details for how to get 790 | another generator. 791 | 792 | h2. Another staffs in Gen class 793 | 794 | To help defining random objects in your class, there are several 795 | functions in Gen class. 796 | 797 | h3. Gen.choose 798 | 799 | Gen.choose(lo, hi) returns a Gen object which generates a random 800 | value in the bound. 801 | 802 | example. 803 |
804 | Gen.choose(0, 10) # a generator of Integer in (0..10)
805 | 
806 | 807 | h3. Gen.frequency 808 | 809 | Gen.frequency requires an array of pair of Integer and Gen objects, 810 | and returns a Gen object. Gen.frequency is used to define the 811 | distribution of randomness. 812 | 813 | example. 814 |
815 | Gen.frequency([[3, Integer.arbitrary], [7, String.arbitrary]])
816 |    # return a random integer or a random string (by choosing
817 |    # randomly) Integer:30% String: 70%
818 | 
819 | 820 | See also SpecialString. 821 | 822 | h3. Gen.lift_array 823 | 824 | Gen.lift_array takes an array and a block which has a variable. 825 | The block should return a Gen object. lift_array returns a Gen 826 | object which generates an array of the result of given block for 827 | applying each member of given array. 828 | 829 | example. 830 | 831 |
832 | class Candy
833 |   extend RushCheck::Arbitrary
834 | 
835 |   def self.arbitrary
836 |     RushCheck::Gen.lift_array([Integer, String]) do |c| 
837 |       c.arbitrary
838 |     end.bind do |args|
839 |       new(*args)
840 |     end
841 |   end
842 | end
843 | 
844 | 845 | h3. Gen.oneof 846 | 847 | Gen.oneof requires an array of Gen objects returns a Gen object. 848 | 849 | example. 850 |
851 | Gen.oneof([Integer.arbitrary, String.arbitrary])
852 |   # return a random integer or a random string (by choosing
853 |   # randomly)
854 | 
855 | 856 | h3. Gen.promote 857 | 858 | Gen.promote is used to generate a random Proc object. 859 | Next section I will describe how to define the random Proc object. 860 | See also proc.rb in the example directory of RushCheck. 861 | 862 | h3. Gen.rand 863 | 864 | Gen.rand is a random number generator. 865 | 866 | h3. Gen.sized 867 | 868 | Gen.sized is used to change the size of random object. 869 | See also some implementation in RushCheck (grep the source!) 870 | 871 | h3. Gen.unit 872 | 873 | return Gen object. 874 | 875 | h3. Gen.vector 876 | 877 | Get a vector of Gen. 878 | 879 | example. 880 |
881 | Gen.vector(Integer, 3) # => Gen [Integer, Integer, Integer]
882 | 
883 | 884 | h2. how to write random Proc which returns objects in YourClass 885 | 886 | It is complecated, and see some examples. 887 | * rushcheck/bool.rb 888 | * rushcheck/integer.rb 889 | * rushcheck/string.rb 890 | 891 | so on, grep 'coarbitrary' 892 | 893 | FIXME: how to write coarbitrary 894 | 895 | h1. Further information 896 | 897 | Webpage should have another useful information: 898 | * "RushCheck Homepage http://rushcheck.rubyforge.org/":http://rushcheck.rubyforge.org/ 899 | 900 | The project page has a bug tracker and webboard. 901 | * "RushCheck at Rubyforge http://rubyforge.org/projects/rushcheck/":http://rubyforge.org/projects/rushcheck/ 902 | 903 | There is no mailing list for RushCheck (now at 2006-08-08), 904 | but don't hesitate to contact to the author! 905 | 906 | Happy hacking and testing! 907 | 908 | 909 | -------------------------------------------------------------------------------- /data/doc/spec.txt: -------------------------------------------------------------------------------- 1 | 2006-11-29 start 2 | 3 | files 4 | ----- 5 | rushcheck.rb 6 | rushcheck/arbitrary.rb rushcheck/claim.rb rushcheck/guard.rb rushcheck/property.rb rushcheck/testable.rb 7 | rushcheck/array.rb rushcheck/config.rb rushcheck/hash.rb rushcheck/random.rb rushcheck/testoptions.rb 8 | rushcheck/assertion.rb rushcheck/float.rb rushcheck/integer.rb rushcheck/result.rb rushcheck/testresult.rb 9 | rushcheck/bool.rb rushcheck/gen.rb rushcheck/proc.rb rushcheck/string.rb rushcheck/version.rb 10 | 11 | 21 files 12 | 13 | categories 14 | ---------- 15 | - prelude 16 | | rushcheck.rb 17 | - version 18 | | version.rb 19 | - arbitrary for base classes 20 | | array.rb 21 | | bool.rb 22 | | float.rb 23 | | hash.rb 24 | | integer.rb 25 | | proc.rb 26 | | string.rb 27 | - random number generators 28 | | random.rb 29 | - interfaces 30 | | assertion.rb 31 | | claim.rb 32 | | guard.rb 33 | - internal preliminaries 34 | | arbitrary.rb 35 | | config.rb 36 | | gen.rb 37 | | property.rb 38 | | result.rb 39 | | testable.rb 40 | | testoptions.rb 41 | | testresult.rb 42 | 43 | specification 44 | ------------- 45 | 46 | -------------------------------------------------------------------------------- /data/examples/candy.rb: -------------------------------------------------------------------------------- 1 | # candy.rb 2 | # an example to write a random generator 3 | 4 | require 'rushcheck' 5 | 6 | class Candy 7 | 8 | extend RushCheck::Arbitrary 9 | 10 | def initialize(name, price) 11 | raise unless price >= 0 12 | @name, @price = name, price 13 | end 14 | 15 | def self.arbitrary 16 | RushCheck::Gen.create(String, Integer) do |name, price| 17 | RushCheck::guard { price >= 0 } 18 | new(name, price) 19 | end 20 | end 21 | 22 | end 23 | 24 | class ExpensiveCandy < Candy 25 | 26 | def initialize(name, price) 27 | raise unless price >= 100000 28 | @name, @price = name, price 29 | end 30 | 31 | def self.arbitrary 32 | lo = 100000 33 | g = RushCheck::Gen.sized { |n| Gen.choose(lo, n + lo)} 34 | xs = [String.arbitrary, g] 35 | RushCheck::Gen.create_by_gen(xs) do |name, price| 36 | RushCheck::guard { price >= 100000 } 37 | new(name, price) 38 | end 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /data/examples/printf.rb: -------------------------------------------------------------------------------- 1 | # malformed format string bug for sprintf 2 | # see also a recent news 3 | # http://k-tai.impress.co.jp/cda/article/news_toppage/30484.html 4 | # this news tells that a japanese handy-phone hungs when it receives 5 | # an email which contains the special format string "%s". 6 | 7 | require 'rushcheck' 8 | 9 | def malformed_format_string 10 | RushCheck::Assertion.new(String) { |s| 11 | sprintf(s) 12 | true 13 | } 14 | end 15 | 16 | def malformed_format_string2 17 | # SpecialString is used to find special format more likely 18 | RushCheck::Assertion.new(SpecialString) { |s| 19 | sprintf(s) 20 | true 21 | } 22 | end 23 | -------------------------------------------------------------------------------- /data/examples/proc.rb: -------------------------------------------------------------------------------- 1 | # proc.rb 2 | # a sample of RushCheck 3 | 4 | require 'rushcheck' 5 | 6 | class Proc 7 | # application 8 | def **(other) 9 | Proc.new do |*args| 10 | res = other.call(*args) 11 | call(*res) 12 | end 13 | end 14 | end 15 | 16 | class MyRandomProc < RandomProc; end 17 | 18 | def associativity_integer 19 | MyRandomProc.set_pattern([Integer], [Integer]) 20 | RushCheck::Assertion.new(MyRandomProc, MyRandomProc, MyRandomProc, Integer) do 21 | |a, b, c, x| 22 | (a ** (b ** c)).call(x) == ((a ** b) ** c).call(x) 23 | end.check 24 | end 25 | 26 | # this test takes much time than associativity_integer 27 | def associativity_string 28 | MyRandomProc.set_pattern([String], [String]) 29 | RushCheck::Assertion.new(MyRandomProc, MyRandomProc, MyRandomProc, String) do 30 | |a, b, c, x| 31 | (a ** (b ** c)).call(x) == ((a ** b) ** c).call(x) 32 | end.check 33 | end 34 | -------------------------------------------------------------------------------- /data/examples/pythagoras.rb: -------------------------------------------------------------------------------- 1 | require 'rushcheck' 2 | 3 | def find_pythagoras 4 | RushCheck::Assertion.new(Integer,Integer,Integer) { 5 | |x, y, z| 6 | RushCheck::guard { x > 0 } 7 | RushCheck::guard { y > 0 } 8 | RushCheck::guard { z > 0 } 9 | (x*x + y*y) != z*z 10 | }.check 11 | end 12 | 13 | def find_p 14 | c = RushCheck::Config.new(1000, 10000, 15 | Proc.new {|x| x / 6 + 1}) 16 | RushCheck::Assertion.new(Integer,Integer,Integer) { 17 | |x, y, z| 18 | RushCheck::guard { x > 0 } 19 | RushCheck::guard { y > 0 } 20 | RushCheck::guard { z > 0 } 21 | # p [x, y, z] 22 | (x*x + y*y) != z*z 23 | }.check(c) 24 | end 25 | -------------------------------------------------------------------------------- /data/examples/roguetile.rb: -------------------------------------------------------------------------------- 1 | # roguetile.rb 2 | # an example of bug patterns 3 | # appeared in "Bug patterns: an introduction - diagnosing and 4 | # correcting recurring bug type in your Java programs", written by 5 | # Eric Allen, 2001. 6 | # http://www-128.ibm.com/developerworks/library/j-diag1.html 7 | 8 | # Tree is an abstract class 9 | class Tree 10 | def add 11 | raise(NotImplementedError, "override!") 12 | end 13 | 14 | def multiply 15 | raise(NotImplementedError, "override!") 16 | end 17 | end 18 | 19 | class Leaf < Tree 20 | 21 | attr_reader :value 22 | def initialize(v) 23 | @value = v 24 | end 25 | 26 | def add 27 | @value.to_i 28 | end 29 | 30 | def multiply 31 | @value.to_i 32 | end 33 | 34 | end 35 | 36 | class Branch < Tree 37 | 38 | attr_reader :value, :left, :right 39 | def initialize(v, l, r) 40 | @value, @left, @right = v, l, r 41 | end 42 | 43 | def add 44 | @value.to_i + @left.add + @right.add 45 | end 46 | 47 | def multiply 48 | # Here, there is a bug in multiply because the auther forgot to 49 | # change after copying the add method. This bug is called Rogue-Tile 50 | # pattern in the article. 51 | 52 | @value.to_i * @left.multiply + @right.multiply # bug! 53 | end 54 | 55 | # To not write a bug such as above, the author of the article 56 | # recommends to use Command-Pattern. See also the article. 57 | # Because the aim of this example is to find a bug, I leave 58 | # a wrong style implementation. 59 | end 60 | 61 | # ok, then let's test them and find a bug. 62 | 63 | require 'rushcheck' 64 | 65 | class Tree 66 | extend RushCheck::Arbitrary 67 | include RushCheck::Coarbitrary 68 | 69 | def self.arbitrary 70 | RushCheck::Gen.frequency([[3, Leaf.arbitrary], [1, Branch.arbitrary]]) 71 | end 72 | 73 | # In this example, coarbitrary isn't needed, however, 74 | # I write them to see how to write coarbitrary. 75 | def coarbitrary 76 | raise(NotImplementedError, "override!") 77 | end 78 | end 79 | 80 | class Leaf < Tree 81 | extend RushCheck::Arbitrary 82 | include RushCheck::Coarbitrary 83 | 84 | def self.arbitrary 85 | Integer.arbitrary.bind {|x| RushCheck::Gen.unit(new(x)) } 86 | end 87 | 88 | def coarbitrary(g) 89 | @value.coarbitrary(g) 90 | end 91 | end 92 | 93 | class Branch < Tree 94 | extend RushCheck::Arbitrary 95 | include RushCheck::Coarbitrary 96 | 97 | def self.arbitrary 98 | RushCheck::Gen.new do |n, g| 99 | g2 = g 100 | v, l, r = [Integer.arbitrary, Tree.arbitrary, Tree.arbitrary].map do |x| 101 | g1, g2 = g2.split 102 | x.value(n, g1) 103 | end 104 | new(v, l, r) 105 | end 106 | end 107 | 108 | def coarbitrary(g) 109 | @value.coarbitrary(@left.coarbitrary(@right.coarbitrary(g))) 110 | end 111 | end 112 | 113 | def prop_leaf_add 114 | RushCheck::Assertion.new(Leaf) {|x| x.add == x.value }.check 115 | end 116 | 117 | def prop_leaf_multiply 118 | RushCheck::Assertion.new(Leaf) {|x| x.multiply == x.value }.check 119 | end 120 | 121 | def prop_branch_add 122 | RushCheck::Assertion.new(Branch) do |t| 123 | t.add == t.value + t.left.add + t.right.add 124 | end.check 125 | end 126 | 127 | # a bad example 128 | def prop_branch_multiply 129 | # If the tester write a wrong test case by copying the code snippet? 130 | # Then we will meet the same problem! Even this test is passed, but 131 | # it is wrong. 132 | RushCheck::Assertion.new(Branch) do |t| 133 | t.add == t.value * t.left.add + t.right.add # Oops, a bug in testcase. 134 | end.check 135 | end 136 | 137 | # But, we can test another /primitive/ properties about 138 | # branch_multiply. 139 | def prop_branch_multiply_zero 140 | RushCheck::Assertion.new(Leaf) do |x| 141 | Branch.new(0, x, x).multiply == 0 142 | end.check 143 | end 144 | 145 | def prop_branch_multiply_power 146 | RushCheck::Assertion.new(Leaf) do |x| 147 | Branch.new(1, x, x).multiply == x.value * x.value 148 | end.check 149 | end 150 | # Yay, then we can find a bug in Branch#multiply. 151 | # In this example, we gain a practical experience that 152 | # don't test by copying the definition, but consider /primitive/ 153 | # properties! 154 | -------------------------------------------------------------------------------- /data/examples/rspec/stack.rb: -------------------------------------------------------------------------------- 1 | # This example is quoted from the RSpec tutorial. 2 | # check also http://rspec.rubyforge.org/tutorials/index.html 3 | 4 | class StackUnderflowError < RuntimeError; end 5 | class StackOverflowError < RuntimeError; end 6 | 7 | class Stack 8 | 9 | SIZE = 5 10 | 11 | def initialize 12 | @items = [] 13 | end 14 | 15 | def empty? 16 | @items.empty? 17 | end 18 | 19 | def length 20 | @items.length 21 | end 22 | 23 | def full? 24 | @items.length == SIZE 25 | end 26 | 27 | def push(item) 28 | raise StackOverflowError if full? 29 | @items.push item 30 | end 31 | 32 | def pop 33 | raise StackUnderflowError if empty? 34 | @items.pop 35 | end 36 | 37 | def peek 38 | raise StackUnderflowError if empty? 39 | @items.last 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /data/examples/rspec/stack_spec.rb: -------------------------------------------------------------------------------- 1 | # This example is quoted from the RSpec tutorial and also 2 | # test/spec. 3 | # check also 4 | # http://rspec.rubyforge.org/tutorials/index.html 5 | # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/217242 6 | # 7 | # To execute this example, you have to follow 8 | # 1. install rspec 9 | # % sudo gem install rspec 10 | # 2. then, execute the 'spec' command 11 | # % spec stack_spec.rb -f s 12 | # 13 | # On my PowerBook PC, this example takes about 50 seconds. 14 | 15 | begin 16 | require 'rubygems' 17 | require_gem 'rushcheck' 18 | rescue LoadError 19 | require 'rushcheck' 20 | end 21 | 22 | def for_all(*cs, &f) 23 | RushCheck::Claim.new(*cs, &f).check.should_equal true 24 | end 25 | 26 | require 'stack' 27 | 28 | context "An empty stack" do 29 | 30 | specify "should not be empty after 'push'" do 31 | for_all(Integer) do |item| 32 | stack = Stack.new 33 | stack.push item 34 | stack.should_not_be_empty 35 | end 36 | end 37 | 38 | end 39 | 40 | # setup for general stack 41 | class NotEmptyAndFullStack < Stack 42 | extend RushCheck::Arbitrary 43 | 44 | def self.arbitrary 45 | stack = self.new 46 | RushCheck::Gen.choose(1, SIZE-2).bind do |size| 47 | RushCheck::Gen.vector(String, size).fmap do |ary| 48 | ary.each { |i| stack.push i } 49 | stack 50 | end 51 | end 52 | end 53 | end 54 | 55 | context "A stack (in general)" do 56 | 57 | setup do 58 | puts " This test consumes time. Please wait few seconds." 59 | end 60 | 61 | specify "should add the top when sent 'push'" do 62 | for_all(NotEmptyAndFullStack, String) do |stack, item| 63 | stack.push item 64 | stack.peek.should_equal item 65 | end 66 | end 67 | 68 | specify "should NOT remove the top item when sent 'peek'" do 69 | for_all(NotEmptyAndFullStack, Integer) do |stack, item| 70 | stack.push item 71 | stack.peek.should_equal item 72 | stack.peek.should_equal item 73 | end 74 | end 75 | 76 | specify "should return the top item when sent 'pop'" do 77 | for_all(NotEmptyAndFullStack, Integer) do |stack, item| 78 | stack.push item 79 | stack.pop.should_equal item 80 | end 81 | end 82 | 83 | specify "should remove the top item when sent 'pop'" do 84 | for_all(NotEmptyAndFullStack, Integer, Integer) do |stack, dummy, item| 85 | stack.push item 86 | stack.push dummy 87 | stack.pop 88 | stack.pop.should_equal item 89 | end 90 | end 91 | 92 | end 93 | 94 | context "An empty stack" do 95 | 96 | specify "should be empty" do 97 | Stack.new.should_be_empty 98 | end 99 | 100 | specify "should no longer be empty" do 101 | for_all(Integer) do |item| 102 | stack = Stack.new 103 | stack.push item 104 | stack.should_not_be_empty 105 | end 106 | end 107 | 108 | specify "should complain when sent 'peek'" do 109 | lambda { Stack.new.peek }.should_raise StackUnderflowError 110 | end 111 | 112 | specify "should complain when sent 'pop'" do 113 | lambda { Stack.new.pop }.should_raise StackUnderflowError 114 | end 115 | end 116 | 117 | context "An almost empty stack (with one item)" do 118 | specify "should not be empty" do 119 | for_all(Integer) do |item| 120 | stack = Stack.new 121 | stack.push item 122 | stack.should_not_be_empty 123 | end 124 | end 125 | 126 | specify "should remain not empty after 'peek'" do 127 | for_all(Integer) do |item| 128 | stack = Stack.new 129 | stack.push item 130 | stack.peek 131 | stack.should_not_be_empty 132 | end 133 | end 134 | 135 | specify "should become empty after 'pop'" do 136 | for_all(Integer) do |item| 137 | stack = Stack.new 138 | stack.push item 139 | stack.pop 140 | stack.should_be_empty 141 | end 142 | end 143 | 144 | end 145 | 146 | # setup for almost full stack 147 | class AlmostFullStack < Stack 148 | extend RushCheck::Arbitrary 149 | 150 | def self.arbitrary 151 | stack = self.new 152 | RushCheck::Gen.vector(String, SIZE-1).fmap do |ary| 153 | ary.each { |i| stack.push i } 154 | stack 155 | end 156 | end 157 | end 158 | 159 | context "An almost full stack (with one item less than capacity)" do 160 | 161 | setup do 162 | puts " This test consumes time. Please wait few seconds." 163 | end 164 | 165 | specify "should not be full" do 166 | for_all(AlmostFullStack) do |stack| 167 | stack.should_not_be_full 168 | end 169 | end 170 | 171 | specify "should become full when sent 'push'" do 172 | for_all(AlmostFullStack, Integer) do |stack, item| 173 | stack.push item 174 | stack.should_be_full 175 | end 176 | end 177 | end 178 | 179 | # setup for full stack 180 | class FullStack < Stack 181 | extend RushCheck::Arbitrary 182 | 183 | def self.arbitrary 184 | stack = self.new 185 | RushCheck::Gen.vector(String, SIZE).fmap do |ary| 186 | ary.each { |i| stack.push i } 187 | stack 188 | end 189 | end 190 | end 191 | 192 | context "A full stack" do 193 | 194 | setup do 195 | puts " This test consumes time. Please wait few seconds." 196 | end 197 | 198 | specify "should be full" do 199 | for_all(FullStack) do |stack| 200 | stack.should_be_full 201 | end 202 | end 203 | 204 | specify "should remain full after 'peek'" do 205 | for_all(FullStack) do |stack| 206 | stack.peek 207 | stack.should_be_full 208 | end 209 | end 210 | 211 | specify "should no longer be full after 'pop'" do 212 | for_all(FullStack) do |stack| 213 | stack.pop 214 | stack.should_not_be_full 215 | end 216 | end 217 | 218 | specify "should complain on 'push'" do 219 | for_all(FullStack, Integer) do |stack, item| 220 | lambda { stack.push item }.should_raise StackOverflowError 221 | end 222 | end 223 | end 224 | -------------------------------------------------------------------------------- /data/examples/sample.rb: -------------------------------------------------------------------------------- 1 | # a simple example 2 | 3 | require 'rushcheck' 4 | 5 | # assert_sort_one should be true 6 | def assert_sort_one 7 | RushCheck::Assertion.new(Integer) { |x| 8 | [x].sort == [x] 9 | }.check 10 | end 11 | 12 | # however, assert_sort_two is not true generally, 13 | # and RushCheck finds a counter example. 14 | def assert_sort_two 15 | RushCheck::Assertion.new(Integer, Integer) { |x, y| 16 | ary = [x, y] 17 | ary.sort == ary 18 | }.check 19 | end 20 | 21 | # if given array is already sorted, then the 22 | # assertion turns true. 23 | def assert_sort_two_sorted 24 | RushCheck::Assertion.new(Integer, Integer) { |x, y| 25 | RushCheck::guard {x <= y} 26 | ary = [x, y] 27 | ary.sort == ary 28 | }.check 29 | end 30 | 31 | # watch statistics 32 | def assert_sort_two_sorted_trivial 33 | RushCheck::Assertion.new(Integer, Integer) { |x, y| 34 | RushCheck::guard {x <= y} 35 | ary = [x, y] 36 | (ary.sort == ary).trivial{x == y} 37 | }.check 38 | end 39 | 40 | def assert_sort_two_sorted_classify 41 | RushCheck::Assertion.new(Integer, Integer) { |x, y| 42 | RushCheck::guard {x <= y} 43 | ary = [x, y] 44 | (ary.sort == ary).classify('same'){x == y}. 45 | classify('bit diff') { (x - y).abs == 1 } 46 | }.check 47 | end 48 | 49 | # Because Assertion.new has to take at least one variable, 50 | # the following example should be failed. 51 | # def assert_sort 52 | # RushCheck::Assertion.new { [].sort == [] }.check 53 | # end 54 | -------------------------------------------------------------------------------- /lib/rushcheck.rb: -------------------------------------------------------------------------------- 1 | # = rushcheck.rb 2 | # this is just a prelude include file 3 | # this provides all of feature of RushCheck for random testing. 4 | 5 | require 'rushcheck/arbitrary' 6 | require 'rushcheck/assertion' 7 | # require 'rushcheck/claim' 8 | require 'rushcheck/gen' 9 | require 'rushcheck/guard' 10 | require 'rushcheck/random' 11 | require 'rushcheck/testable' 12 | require 'rushcheck/version' 13 | 14 | require 'rushcheck/array' 15 | require 'rushcheck/bool' 16 | require 'rushcheck/float' 17 | require 'rushcheck/hash' 18 | require 'rushcheck/integer' 19 | require 'rushcheck/proc' 20 | require 'rushcheck/string' 21 | -------------------------------------------------------------------------------- /lib/rushcheck/arbitrary.rb: -------------------------------------------------------------------------------- 1 | # = arbitrary.rb 2 | # 3 | # This file consists of two modules _Arbitrary_ and _Coarbitrary_. 4 | # They are abstract modules. 5 | # _Arbitrary_ provides an instance method _arbitrary_. 6 | # On the other hand, _Coarbitrary_ provides a class method 7 | # _coarbitrary_. They are abstract methods and should be overrided in 8 | # each class after *extend/include* them. See the following example. 9 | # 10 | # == Example how to use 11 | # 12 | # You have to define the overrided methods both _arbitrary_ and 13 | # _coarbitrary_ at +YourClass+ to generate random test instances. 14 | # 15 | # See also the manual how to build a random generator. 16 | # 17 | # require 'rushcheck/arbitrary' 18 | # 19 | # class YourClass 20 | # extend RushCheck::Arbitrary 21 | # include RushCheck::Coarbitrary 22 | # 23 | # def self.arbitrary 24 | # # must be overrided 25 | # end 26 | # 27 | # def coarbitrary 28 | # # must be overrided, too 29 | # end 30 | # end 31 | # 32 | 33 | module RushCheck 34 | 35 | def _message_should_be_overrided # :nodoc: 36 | /^.+?:\d+(?::in (`.*'))?/ =~ caller.first 37 | [ "The method", $1, "should be overrided at", 38 | self.class.to_s ].join(" ") + "." 39 | end 40 | 41 | private :_message_should_be_overrided 42 | 43 | module Arbitrary 44 | 45 | include RushCheck 46 | 47 | # A generator for values of the object. 48 | # It is an instance-specific method. The method must be 49 | # overrided as a instance method, and return a Gen object 50 | # with the same class of self. 51 | def arbitrary 52 | raise(NotImplementedError, _message_should_be_overrided) 53 | end 54 | 55 | end 56 | 57 | module Coarbitrary 58 | 59 | include RushCheck 60 | 61 | # Used to generate a function of self to other. 62 | # The method must be overrided as a class method which 63 | # takes one argument of Gen and return a Gen object. 64 | # You can often use Gen.variant to implement. 65 | def coarbitrary(g) 66 | raise(NotImplementedError, _message_should_be_overrided) 67 | end 68 | 69 | end 70 | 71 | end 72 | -------------------------------------------------------------------------------- /lib/rushcheck/array.rb: -------------------------------------------------------------------------------- 1 | # = array.rb 2 | # The file provides a random generator of arrays. 3 | 4 | require 'rushcheck/arbitrary' 5 | require 'rushcheck/error' 6 | require 'rushcheck/gen' 7 | require 'rushcheck/testable' 8 | 9 | # class RandomArray acts a random generator of arrays. 10 | # Programmer can make a subclass of RandomArray to get 11 | # user defined generators. 12 | class RandomArray < Array 13 | 14 | extend RushCheck::Arbitrary 15 | include RushCheck::Coarbitrary 16 | 17 | # self.set_pattern must be executed before calling self.arbitrary. 18 | # The method defines pattern of random arrays for self.arbitrary. 19 | # This takes a block, where the variable should be a class which 20 | # is used for the first element of random array. 21 | # 22 | # For example, the following code create a class of array which 23 | # consists of random integers; 24 | # 25 | # require 'rushcheck' 26 | # 27 | # class IntegerRandomArray < RandomArray; end 28 | # # for all index i, components belong to Integer 29 | # IntegerRandomArray.set_pattern {|i| Integer} 30 | # 31 | # RushCheck::Assertion.new(IntegerRandomArray) { |arr| 32 | # ... # test codes here 33 | # } 34 | # 35 | def self.set_pattern(&f) 36 | @proc = f 37 | nil 38 | end 39 | 40 | def self.generate_array(len) 41 | RushCheck::Gen.new do |n, r| 42 | ary = [] 43 | r2 = r 44 | (0..len).each do |i| 45 | r1, r2 = r2.split 46 | ary << @proc.call(i).arbitrary.value(n, r1) 47 | end 48 | 49 | ary 50 | end 51 | end 52 | 53 | def self.arrange_len 54 | RushCheck::Gen.sized do |m| 55 | m = 1 - m if m <= 0 56 | RushCheck::Gen.choose(0, m).bind do |len| 57 | yield len 58 | end 59 | end 60 | end 61 | 62 | def self.arbitrary 63 | self.arrange_len do |len| 64 | if len == 0 65 | then RushCheck::Gen.unit([]) 66 | else self.generate_array(len) 67 | end 68 | end 69 | end 70 | 71 | def coarbitrary(g) 72 | r = g.variant(0) 73 | each do |c| 74 | r = c.coarbitrary(r.variant(1)) 75 | end 76 | r 77 | end 78 | 79 | end 80 | 81 | class NonEmptyRandomArray < RandomArray 82 | 83 | def self.arbitrary 84 | self.arrange_len do |len| 85 | self.generate_array(len + 1) 86 | end 87 | end 88 | 89 | end 90 | -------------------------------------------------------------------------------- /lib/rushcheck/assertion.rb: -------------------------------------------------------------------------------- 1 | # = assertion.rb 2 | # this file provides a class Assertion for random testing. 3 | 4 | require 'rushcheck/error' 5 | require 'rushcheck/gen' 6 | require 'rushcheck/guard' 7 | require 'rushcheck/property' 8 | require 'rushcheck/result' 9 | require 'rushcheck/testable' 10 | 11 | module RushCheck 12 | 13 | # Assertion class is one of main features of RushCheck. 14 | # You can write a testcase for random testing as follows: 15 | # 16 | # RushCheck::Assertion.new(Integer, String) do |n, s| 17 | # RushCheck::guard { precondition } 18 | # body 19 | # end.check 20 | # 21 | # The return value of the body of testcase should be 22 | # true or false and checked by the method 'check'. 23 | # 24 | # Note that the number of arguments in the block must be 25 | # equal to the number of arguments of Assertion.new. 26 | # Otherwise an exception is raised. 27 | # 28 | # See also the tutorial and several examples. 29 | 30 | class Assertion 31 | 32 | include RushCheck::InternalError 33 | include RushCheck::Testable 34 | 35 | # The body in the test as a block 36 | attr_reader :body 37 | 38 | # Create a random test code. The argument _xs_ should be 39 | # classes. The block _f_ takes variables as same as the 40 | # number of _xs_. This should be return true or false. 41 | def initialize(*xs, &f) 42 | 43 | err_n = [ "Incorrect number of variables:", 44 | "( #{xs.length} for #{f.arity} )" ].join(' ') 45 | if f.arity == -1 46 | raise(RushCheckError, err_n) # unless xs.empty? 47 | elsif xs.length != f.arity 48 | raise(RushCheckError, err_n) 49 | end 50 | 51 | xs.each do |x| 52 | err_c = ["Illegal variable which is not Class:", 53 | x.inspect].join(' ') 54 | raise(RushCheckError, err_c) unless x.class === Class 55 | end 56 | 57 | @inputs = xs[0..(f.arity - 1)] 58 | @body = f 59 | end 60 | 61 | def property #:nodoc: 62 | g = RushCheck::Gen.new do |n, r| 63 | r2 = r 64 | if @inputs 65 | then 66 | @inputs.map do |c| 67 | r1, r2 = r2.split 68 | c.arbitrary.value(n, r1) 69 | end 70 | else 71 | [] 72 | end 73 | end.bind do |args| 74 | test = begin 75 | r = @body.call(*args) 76 | unless r == true || r == false 77 | err = ["The body of Assertion.new should be", 78 | "true or false, but:", 79 | test.inspect].join(' ') 80 | raise(RushCheckError, err) 81 | end 82 | r 83 | rescue Exception => ex 84 | case ex 85 | when RushCheckError 86 | raise ex 87 | when RushCheck::GuardException 88 | RushCheck::Result.new(nil) 89 | else 90 | err = "Unexpected exception: #{ex.inspect}\n" + 91 | ex.backtrace.join("\n") 92 | RushCheck::Result.new(false, [], [err]) 93 | end 94 | end 95 | # not use ensure here because ensure clause 96 | # does not return values 97 | test.property.gen.fmap do |res| 98 | res.arguments << args.inspect 99 | res 100 | end 101 | end 102 | 103 | RushCheck::Property.new(g) 104 | end 105 | 106 | end 107 | 108 | end 109 | -------------------------------------------------------------------------------- /lib/rushcheck/bool.rb: -------------------------------------------------------------------------------- 1 | # = bool.rb 2 | # 3 | # This file extends the default Ruby's class TrueClass and FalseClass 4 | # for RushCheck. 5 | 6 | require 'rushcheck/arbitrary' 7 | require 'rushcheck/integer' 8 | require 'rushcheck/random' 9 | require 'rushcheck/result' 10 | require 'rushcheck/testable' 11 | 12 | module RushCheck 13 | module RandomBool 14 | def arbitrary 15 | RushCheck::Gen.elements([true, false]) 16 | end 17 | 18 | def random_range(gen, lo=@@min_bound, hi=@@max_bound) 19 | v, g = Integer.random_range(gen, 0, 1) 20 | [v==0, g] 21 | end 22 | end 23 | end 24 | 25 | class TrueClass 26 | 27 | extend RushCheck::Arbitrary 28 | extend RushCheck::HsRandom 29 | extend RushCheck::RandomBool 30 | 31 | include RushCheck::Testable 32 | include RushCheck::Coarbitrary 33 | 34 | @@min_bound = 0 35 | @@max_bound = 1 36 | 37 | def self.bound 38 | [@@min_bound, @@max_bound] 39 | end 40 | 41 | def coarbitrary(g) 42 | g.variant(0) 43 | end 44 | 45 | def property 46 | RushCheck::Result.new(self).result 47 | end 48 | 49 | end 50 | 51 | class FalseClass 52 | 53 | extend RushCheck::Arbitrary 54 | extend RushCheck::HsRandom 55 | extend RushCheck::RandomBool 56 | 57 | include RushCheck::Coarbitrary 58 | include RushCheck::Testable 59 | 60 | @@min_bound = 0 61 | @@max_bound = 1 62 | 63 | def self.bound 64 | [@@min_bound, @@max_bound] 65 | end 66 | 67 | def coarbitrary(g) 68 | g.variant(1) 69 | end 70 | 71 | def property 72 | RushCheck::Result.new(self).result 73 | end 74 | end 75 | 76 | class NilClass 77 | extend RushCheck::Arbitrary 78 | 79 | include RushCheck::Coarbitrary 80 | include RushCheck::Testable 81 | 82 | def self.arbitrary 83 | Gen.unit(nil) 84 | end 85 | 86 | def coarbitrary(g) 87 | g.variant(0) 88 | end 89 | 90 | def property 91 | RushCheck::Result.nothing 92 | end 93 | 94 | end 95 | -------------------------------------------------------------------------------- /lib/rushcheck/config.rb: -------------------------------------------------------------------------------- 1 | # = Config.rb 2 | # This file is implemented for the class Config. 3 | 4 | require 'rushcheck/testresult' 5 | 6 | module RushCheck 7 | 8 | # Config is a class which has configurations of tests. 9 | # This class is needed for several test functions. 10 | class Config 11 | 12 | attr_reader :max_test, :max_fail, :size, :every 13 | 14 | def self.verbose 15 | new(100, 1000, 16 | Proc.new{|x| x / 2 + 3}, 17 | Proc.new do |n, args| 18 | n.to_s + ":\n" + args.join("\n") + "\n" 19 | end) 20 | end 21 | 22 | def self.silent 23 | new(100, 1000, 24 | Proc.new{|x| x / 2 + 3}, 25 | Proc.new { |n, args| ''}) 26 | end 27 | 28 | def self.batch(n, v) 29 | new(n, n * 10, 30 | Proc.new{|x| (x / 2 + 3).to_i}, 31 | Proc.new do |n, args| 32 | v ? n.to_s + ":\n" + args.join("\n") + "\n" : "" 33 | end) 34 | end 35 | 36 | def initialize(max_test = 100, 37 | max_fail = 1000, 38 | size = Proc.new {|x| x / 2 + 3}, 39 | every = Proc.new {|n, args| s = n.to_s; s + ("\b" * s.length)}) 40 | @max_test, @max_fail, @size, @every = max_test, max_fail, size, every 41 | end 42 | 43 | # print results of tests. 44 | def done(mesg, ntest, stamps) 45 | print mesg + ' ' + ntest.to_s + ' tests' 46 | 47 | bag = stamps.compact.find_all {|x| ! x.empty?}.sort. 48 | inject(Hash.new) do |r, m| 49 | r[m] = r[m].nil? ? 1 : r[m] + 1 50 | r 51 | end.sort_by {|k, v| v}.reverse.map do |pair| 52 | percentage = ((100 * pair[1]) / ntest).to_s + '%' 53 | ([percentage] + pair[0]).join(', ') 54 | end 55 | 56 | mes = case bag.length 57 | when 0 58 | ".\n" 59 | when 1 60 | '(' + bag[0] + ").\n" 61 | else 62 | ".\n" + bag.join(".\n") + ".\n" 63 | end 64 | 65 | print mes 66 | end 67 | 68 | # execute tests 69 | def tests(gen, rnd, nt=1, nf=0, stamps=[]) 70 | ntest, nfail = nt, nf 71 | while true 72 | if ntest > max_test 73 | done('OK, passed', max_test, stamps) 74 | tests_result = true 75 | break 76 | end 77 | if nfail > max_fail 78 | done('Arguments exhausted after ', ntest, stamps) 79 | tests_result = nil 80 | break 81 | end 82 | 83 | rnd_l, rnd_r = rnd.split 84 | result = gen.generate(size.call(ntest), rnd_r) 85 | message = every.call(ntest, result.arguments) 86 | print message # don't puts 87 | 88 | case result.ok 89 | when nil 90 | nfail += 1 91 | redo 92 | when true 93 | stamps.push(result.stamp) 94 | ntest += 1 95 | redo 96 | when false 97 | error = "Falsifiable, after " + ntest.to_s + " tests:\n" + 98 | result.arguments.join("\n") 99 | puts error 100 | tests_result = false 101 | break 102 | else 103 | raise(RuntimeError, "RushCheck: illegal result") 104 | end 105 | end 106 | 107 | tests_result 108 | end 109 | 110 | def test_batch(gen, rnd, nt=1, nf=0, stamps=[]) 111 | ntest, nfail = nt, nf 112 | while true 113 | if ntest > max_test 114 | tests_result = RushCheck::TestOk.new("OK, passed", ntest, stamps) 115 | break 116 | end 117 | if nfail > max_fail 118 | test_result = RushCheck::TestExausted.new('Arguments exhausted after ', ntest, stamps) 119 | break 120 | end 121 | 122 | rnd_l, rnd_r = rnd.split 123 | result = gen.generate(size.call(ntest), rnd_r) 124 | message = every.call(ntest, result.arguments) 125 | print message # don't puts 126 | 127 | case result.ok 128 | when nil 129 | nfail += 1 130 | redo 131 | when true 132 | stamps.push(result.stamp) 133 | ntest += 1 134 | redo 135 | when false 136 | tests_result = RushCheck::TestFailed.new(result.arguments, ntest) 137 | break 138 | else 139 | raise(RuntimeError, "RushCheck: illegal result") 140 | end 141 | end 142 | 143 | test_result 144 | end 145 | 146 | end 147 | 148 | end 149 | -------------------------------------------------------------------------------- /lib/rushcheck/error.rb: -------------------------------------------------------------------------------- 1 | # = error.rb 2 | # define the original exception for RushCheck 3 | 4 | module RushCheck 5 | 6 | module InternalError 7 | 8 | class RushCheckError < StandardError; end 9 | 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/rushcheck/float.rb: -------------------------------------------------------------------------------- 1 | # = float.rb 2 | # an extension to Float class for RushCheck 3 | 4 | require 'rushcheck/arbitrary' 5 | require 'rushcheck/integer' 6 | require 'rushcheck/random' 7 | 8 | class Float 9 | 10 | extend RushCheck::Arbitrary 11 | extend RushCheck::HsRandom 12 | 13 | include RushCheck::Coarbitrary 14 | 15 | @@min_bound = 0.0 16 | @@max_bound = 1.0 17 | 18 | def self.arbitrary 19 | RushCheck::Gen.new do |n, r| 20 | a, b, c = (1..3).map { Integer.arbitrary.value(n, r) } 21 | a.to_f + (b.to_f / (c.abs.to_f + 1.0)) 22 | end 23 | end 24 | 25 | def self.bound 26 | [@@min_bound, @@max_bound] 27 | end 28 | 29 | def self.random_range(gen, lo=@@min_bound, hi=@@max_bound) 30 | x, g = Integer.random(gen) 31 | a, b = Integer.bound 32 | r = (lo+hi/2).to_f + ((hi - lo).to_f / (b - a) * x) 33 | 34 | [r, g] 35 | end 36 | 37 | def coarbitrary(g) 38 | h = truncate 39 | t = (self * (10 ** ((self - h).to_s.length - 2))).truncate 40 | h.coarbitrary(t.coarbitrary(g)) 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /lib/rushcheck/gen.rb: -------------------------------------------------------------------------------- 1 | # = gen.rb 2 | # 3 | # This file is implemented the type class Gen in Haskell's QuickCheck. 4 | # Almost all implementations are similar to Haskell's one. 5 | # Therefore check also the Haskell implementation. 6 | 7 | require 'rushcheck/gen' 8 | require 'rushcheck/integer' 9 | 10 | module RushCheck 11 | 12 | # Gen provides functions for generating test instances. 13 | class Gen 14 | 15 | @@max_create = 10000 16 | 17 | # choose is one of primitive generators to create a random Gen object. 18 | # choose returns a Gen object which generates a random value in the 19 | # bound. It may useful to implement arbitrary method into your class. 20 | def self.choose(lo=nil, hi=nil) 21 | rand.fmap do |x| 22 | lo.class.random(x, lo, hi).first 23 | end 24 | end 25 | 26 | # create_gen is one of primitive generators to create a random Gen object. 27 | # create_gen takes an array of Gen objects, and any block to generate object. 28 | # Then create_gen returns a Gen object. It may useful to implement 29 | # arbitrary method into your class. 30 | def self.create_by_gen(xs, &f) 31 | self.new do |n, r| 32 | r2 = r 33 | 34 | try = 0 35 | begin 36 | if try > @@max_create 37 | raise(RuntimeError, "Failed to guards too many in the assertion.") 38 | end 39 | args = xs.map do |gen| 40 | r1, r2 = r2.split 41 | gen.value(n, r1) 42 | end 43 | f.call(*args) 44 | rescue Exception => ex 45 | case ex 46 | when RushCheck::GuardException 47 | try += 1 48 | retry 49 | else 50 | raise(ex, ex.to_s) 51 | end 52 | end 53 | end 54 | end 55 | 56 | # create is one of primitive generators to create a random Gen object. 57 | # create takes at least a class, and any block to generate object. 58 | # Then create returns a Gen object. It may useful to implement 59 | # arbitrary method into your class. 60 | def self.create(*cs, &f) 61 | self.create_by_gen(cs.map {|c| c.arbitrary}, &f) 62 | end 63 | 64 | # elements is one of primitive generators to create a random Gen 65 | # object. elements requires an array and returns a Gen object which 66 | # generates an object in the array randomly. It may useful to 67 | # implement arbitrary method into your class. 68 | def self.elements(xs) 69 | raise(RuntimeError, "given argument is empty") if xs.empty? 70 | 71 | choose(0, xs.length - 1).fmap {|i| xs[i] } 72 | end 73 | 74 | # frequency is one of primitive generators to create a random Gen 75 | # object. frequency requires an array of pairs and returns a Gen 76 | # object. The first component of pair should be a positive Integer 77 | # and the second one should be a Gen object. The integer acts as a 78 | # weight for choosing random Gen object in the array. For example, 79 | # frequency([[1, Gen.rand], [2, Integer.arbitrary]]) returns the 80 | # random generator Gen.rand in 33%, while another random generator 81 | # of Integer (Integer.arbitrary) in 67%. 82 | def self.frequency(xs) 83 | tot = xs.inject(0) {|r, pair| r + pair.first} 84 | raise(RuntimeError, "Illegal frequency:#{xs.inspect}") if tot == 0 85 | choose(0, tot - 1).bind do |n| 86 | m = n 87 | xs.each do |pair| 88 | if m <= pair[0] 89 | then break pair[1] 90 | else m -= pair[0] 91 | end 92 | end 93 | end 94 | end 95 | 96 | # lift_array is one of primitive generators to create a randam Gen 97 | # object. lift_array takes an array and a block which has a 98 | # variable. The block should return a Gen object. lift_array returns 99 | # a Gen object which generates an array of the result of given block 100 | # for applying each member of given array. 101 | def self.lift_array(xs) 102 | self.new do |n, r| 103 | r2 = r 104 | xs.map do |c| 105 | r1, r2 = r2.split 106 | yield.value(n, r1) 107 | end 108 | end 109 | end 110 | 111 | # oneof is /one of/ primitive generators to create a random Gen object. 112 | # oneof requires an array of Gen objects, and returns a Gen object 113 | # which choose a Gen object in the array randomly. 114 | # It may useful to implement arbitrary method into your class. 115 | def self.oneof(gens) 116 | elements(gens).bind {|x| x} 117 | end 118 | 119 | # promote is the function to create a Gen object which generates a 120 | # procedure (Proc). promote requires a block which takes one 121 | # variable and the block should be return a Gen object. 122 | # promote returns a Gen object which generate a new procedure 123 | # with the given block. 124 | # It may useful to implement coarbitrary method into your class. 125 | def self.promote 126 | new {|n, r| Proc.new {|a| yield(a).value(n, r) } } 127 | end 128 | 129 | # rand returns a Gen object which generates a random number 130 | # generator. 131 | def self.rand 132 | new {|n, r| r} 133 | end 134 | 135 | # sized is a combinator which the programmer can use to access the 136 | # size bound. It requires a block which takes a variable as an 137 | # integer for size. The block should be a function which changes the 138 | # size of random instances. 139 | def self.sized 140 | new {|n, r| yield(n).value(n, r) } 141 | end 142 | 143 | # unit is a monadic function which equals the return function in 144 | # the Haskell's monad. It requires one variable and returns a Gen 145 | # object which generates the given object. 146 | def self.unit(x) 147 | new {|n, r| x} 148 | end 149 | 150 | # vector is one of primitive generators to create a Gen object. 151 | # vector takes two variables, while the first one should be class, 152 | # and the second one should be length. vector returns a Gen object 153 | # which generates an array whose components belongs the given class 154 | # and given length. 155 | def self.vector(c, len) 156 | new do |n, r| 157 | r2 = r 158 | (1..len).map do 159 | r1, r2 = r2.split 160 | c.arbitrary.value(n, r1) 161 | end 162 | end 163 | end 164 | 165 | # to initialize Gen object, it requires a block which takes two 166 | # variables. The first argument of block is assumed as an integer, 167 | # and the second one is assumed as a random generator of RandomGen. 168 | def initialize(&f) 169 | @proc = f 170 | end 171 | 172 | # bind is a monadic function such as Haskel's (>>=). 173 | # bind takes a block which has a variable where is the return value 174 | # of the Gen object. The block should return a Gen object. 175 | def bind 176 | self.class.new do |n, r| 177 | r1, r2 = r.split 178 | yield(value(n, r1)).value(n, r2) 179 | end 180 | end 181 | 182 | # value is a method to get the value of the internal procedure. 183 | # value takes two variables where the first argument is assumed as 184 | # an integer and the second one is assumed as a random generator of 185 | # RandomGen. 186 | def value(n, g) 187 | @proc.call(n, g) 188 | end 189 | 190 | # fmap is a categorical function as same in Haskell. 191 | # fmap requires a block which takes one variable. 192 | def fmap 193 | bind {|x| self.class.unit(yield(x)) } 194 | end 195 | 196 | # forall is a function to create a Gen object. 197 | # forall requires a block which takes any variables 198 | # and returns a Property object. Then forall returns 199 | # a generator of the property. 200 | def forall 201 | bind do |*a| 202 | yield(*a).property.gen.bind do |res| 203 | res.arguments.push(a.to_s) 204 | self.class.unit(res) 205 | end 206 | end 207 | end 208 | 209 | # generate returns the random instance. generates takes two 210 | # variables, where the first one should be an integer and the second 211 | # should be the random number generator such as StdGen. 212 | def generate(n, rnd) 213 | s, r = Integer.random(rnd, 0, n) 214 | value(s, r) 215 | end 216 | 217 | # resize returns another Gen object which resized by the given 218 | # paramater. resize takes one variable in Integer. 219 | def resize(n) 220 | self.class.new {|x, r| value(n, r) } 221 | end 222 | 223 | # variant constructs a generator which transforms the random number 224 | # seed. variant takes one variable which should be an 225 | # Integer. variant is needed to generate random functions. 226 | def variant(v) 227 | self.class.new do |n, r| 228 | gen = r 229 | v.times { gen, dummy = gen.split } 230 | value(n, gen) 231 | end 232 | end 233 | 234 | end 235 | 236 | end 237 | 238 | -------------------------------------------------------------------------------- /lib/rushcheck/guard.rb: -------------------------------------------------------------------------------- 1 | # = guard.rb 2 | # This provides module functions guard and its friends 3 | # 4 | 5 | module RushCheck 6 | 7 | class GuardException < StandardError; end 8 | 9 | def guard 10 | raise RushCheck::GuardException unless yield 11 | end 12 | 13 | def guard_raise(c) 14 | begin 15 | yield 16 | rescue Exception => ex 17 | case ex 18 | when c 19 | raise RushCheck::GuardException 20 | else 21 | raise ex 22 | end 23 | end 24 | end 25 | module_function :guard, :guard_raise 26 | 27 | end 28 | -------------------------------------------------------------------------------- /lib/rushcheck/hash.rb: -------------------------------------------------------------------------------- 1 | # = hash.rb 2 | # a random generator of Hash 3 | require 'rushcheck/arbitrary' 4 | require 'rushcheck/gen' 5 | require 'rushcheck/testable' 6 | 7 | # RandomHash is a subclass of Hash which provides a random generator 8 | # of hash. Programmer can make a subclass of RandomHash to make user 9 | # defined random generator of Hash. 10 | class RandomHash < Hash 11 | 12 | extend RushCheck::Arbitrary 13 | include RushCheck::Coarbitrary 14 | 15 | # class method set_pattern takes a hash object of 16 | # random pattern. For example, the following pattern 17 | # pat = { 'key1' => Integer, 'key2' => String } 18 | # means that an arbitrary hash is randomly generated 19 | # which has two keys (say 'key1' and 'key2') and 20 | # has indicated random object as its values. 21 | def self.set_pattern(pat) 22 | @@pat = pat 23 | self 24 | end 25 | 26 | def self.arbitrary 27 | RushCheck::Gen.new do |n, r| 28 | h = {} 29 | r2 = r 30 | @@pat.keys.each do |k| 31 | r1, r2 = r2.split 32 | h[k] = @@pat[k].arbitrary.value(n, r1) 33 | end 34 | h 35 | end 36 | end 37 | 38 | def coarbitrary(g) 39 | r = g.variant(0) 40 | values.each do |c| 41 | r = c.coarbitrary(r.variant(1)) 42 | end 43 | r 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /lib/rushcheck/integer.rb: -------------------------------------------------------------------------------- 1 | # = integer.rb 2 | # an extension to Integer class for RushCheck 3 | # 4 | 5 | require 'rushcheck/arbitrary' 6 | require 'rushcheck/gen' 7 | require 'rushcheck/random' 8 | 9 | # ruby's Integer class is extended to use RushCheck library. 10 | # See also HsRandom, Arbitrary and Coarbitrary. 11 | class Integer 12 | extend RushCheck::HsRandom 13 | extend RushCheck::Arbitrary 14 | 15 | include RushCheck::Coarbitrary 16 | 17 | @@max_bound = 2**30 - 1 18 | @@min_bound = -(2**30) 19 | 20 | # this method is needed to include HsRandom. 21 | def self.bound 22 | [@@min_bound, @@max_bound] 23 | end 24 | 25 | # this method is needed to use Arbitrary. 26 | def self.arbitrary 27 | RushCheck::Gen.sized {|n| RushCheck::Gen.choose(-n, n) } 28 | end 29 | 30 | # this method is needed to include HsRandom. 31 | def self.random_range(gen, lo=@@min_bound, hi=@@max_bound) 32 | hi, lo = lo, hi if hi < lo 33 | v, g = gen.gen_next 34 | d = hi - lo + 1 35 | 36 | if d == 1 37 | then [lo, g] 38 | else [(v % d) + lo, g] 39 | end 40 | end 41 | 42 | # this method is needed to use Coarbitrary. 43 | def coarbitrary(g) 44 | m = (self >= 0) ? 2 * self : (-2) * self + 1 45 | g.variant(m) 46 | end 47 | end 48 | 49 | class PositiveInteger < Integer 50 | 51 | @@max_bound = 2**30 - 1 52 | @@min_bound = 1 53 | 54 | # this method is needed to include HsRandom. 55 | def self.random_range(gen, lo=@@min_bound, hi=@@max_bound) 56 | hi, lo = lo, hi if hi < lo 57 | raise RuntimeError, "PositiveInteger requires positive lower bound." if lo <= 0 58 | v, g = gen.gen_next 59 | d = hi - lo + 1 60 | 61 | if d == 1 62 | then [lo, g] 63 | else [(v % d) + lo, g] 64 | end 65 | end 66 | 67 | # this method is needed to use Arbitrary. 68 | def self.arbitrary 69 | RushCheck::Gen.sized do |n| 70 | n = 1 - n if n <= 0 71 | RushCheck::Gen.choose(1, n) 72 | end 73 | end 74 | 75 | end 76 | 77 | class NegativeInteger < Integer 78 | 79 | @@max_bound = -1 80 | @@min_bound = -(2**30) 81 | 82 | # this method is needed to include HsRandom. 83 | def self.random_range(gen, lo=@@min_bound, hi=@@max_bound) 84 | hi, lo = lo, hi if hi < lo 85 | raise RuntimeError, "NegativeInteger requires negative upper bound." if hi >= 0 86 | v, g = gen.gen_next 87 | d = hi - lo + 1 88 | 89 | if d == 1 90 | then [lo, g] 91 | else [(v % d) + lo, g] 92 | end 93 | end 94 | 95 | # this method is needed to use Arbitrary. 96 | def self.arbitrary 97 | RushCheck::Gen.sized do |n| 98 | n = (-1) - n if n >= 0 99 | RushCheck::Gen.choose(n, -1) 100 | end 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /lib/rushcheck/proc.rb: -------------------------------------------------------------------------------- 1 | # = proc.rb 2 | # The file provides a random generator of procedure. 3 | # See also examples/proc.rb 4 | 5 | require 'rushcheck/arbitrary' 6 | require 'rushcheck/gen' 7 | require 'rushcheck/testable' 8 | 9 | class RandomProc < Proc 10 | 11 | extend RushCheck::Arbitrary 12 | include RushCheck::Coarbitrary 13 | include RushCheck::Testable 14 | 15 | def self.set_pattern(inputs, outputs) 16 | @@inputs, @@outputs = inputs, outputs 17 | end 18 | 19 | def self.arbitrary 20 | RushCheck::Gen.new do |n, r| 21 | Proc.new do |*args| 22 | gens = if args.empty? 23 | then @@outputs.map {|c| c.arbitrary } 24 | else args.map do |v| 25 | @@outputs.map do |c| 26 | v.coarbitrary(c.arbitrary) 27 | end 28 | end.flatten 29 | end 30 | RushCheck::Gen.oneof(gens).value(n, r) 31 | end 32 | end 33 | end 34 | 35 | def coarbitrary(g) 36 | RushCheck::Gen.new do |n, r| 37 | r2 = r 38 | args = @@inputs.map do |c| 39 | r1, r2 = r2.split 40 | c.arbitrary.value(n, r1) 41 | end 42 | 43 | call(*args).coarbitrary(g) 44 | end 45 | end 46 | 47 | def property 48 | RushCheck::Gen.lift_array(@@inputs) {|c| c.arbitrary }.forall do |*args| 49 | call(*args) 50 | end 51 | end 52 | 53 | end 54 | -------------------------------------------------------------------------------- /lib/rushcheck/property.rb: -------------------------------------------------------------------------------- 1 | # = property.rb 2 | # This file defines properties of testcase 3 | # class Property is used for RushCheck internally 4 | require 'rushcheck/gen' 5 | require 'rushcheck/result' 6 | require 'rushcheck/testable' 7 | 8 | module RushCheck 9 | 10 | class Property 11 | 12 | include RushCheck::Testable 13 | 14 | attr_reader :gen 15 | def initialize(obj=nil, stamp=[], arguments=[]) 16 | case obj 17 | when nil, true, false 18 | result = RushCheck::Result.new(obj, stamp, arguments) 19 | @gen = RushCheck::Gen.unit(result) 20 | when Gen 21 | @gen = obj 22 | else 23 | raise(RuntimeError, "illegal arguments") 24 | end 25 | end 26 | 27 | def property 28 | self 29 | end 30 | 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lib/rushcheck/random.rb: -------------------------------------------------------------------------------- 1 | # = random.rb - A library for psuedo-random number from Haskell 2 | # 3 | # This library deals with the common task of 4 | # psuedo-random number generation from Haskell98. The library 5 | # makes it possible to generate repeatable results, by starting 6 | # with a specified initial random number generator; or to get 7 | # different results on each run by using the system-initialised 8 | # generator, or by supplying a seed from some other source. 9 | # 10 | # This file provides two module RandomGen, HsRandom, two classes 11 | # StdGen and TheStdGen. 12 | 13 | module RushCheck 14 | 15 | # The module RandomGen is an abstract module. This is included 16 | # to add instance methods. The author considers that few developers 17 | # need to read this implementation. See StdGen as an example. 18 | # Check also Haskell's System.Random library. 19 | module RandomGen 20 | 21 | # gen_next should be overrided as an instance method. 22 | # It is assumed that gen_next returns an array with length 2. 23 | # Here, call 'Foo' the class which includes RandomGen. 24 | # The first components of the result should be an integer, and the 25 | # last components should be an object which belongs to the class 26 | # Foo. 27 | def gen_next 28 | raise_not_implemented_error 29 | end 30 | 31 | # split should be overrided as an instance method. 32 | # It is assumed that split returns an array with length 2. 33 | # Here, call 'Foo' the class which includes RandomGen. 34 | # Then the components of the result are objects which belongs to the 35 | # class Foo. 36 | def split 37 | raise_not_implemented_error 38 | end 39 | 40 | # gen_range should be overrided as an instance method. 41 | # It is assumed that split returns an array with length 2. 42 | # Here, call 'Foo' the class which includes RandomGen. 43 | # Then the components of the result are integers where 44 | # the first components should be the lowest bound of 45 | # the first components of gen_next. Another should be 46 | # the highest bound of the first components of gen_next. 47 | def gen_range 48 | raise_not_implemented_error 49 | end 50 | 51 | private 52 | def raise_not_implemented_error 53 | raise(NotImplementedError, "This method should be overrided.") 54 | end 55 | 56 | end 57 | 58 | # StdGen is a class (and the unique class) which includes RandomGen. 59 | # This class provides a functional random number generator. 60 | class StdGen 61 | 62 | include RushCheck::RandomGen 63 | 64 | @@min_bound = -(2**30) 65 | @@max_bound = 2**30 - 1 66 | 67 | # left and right are two seeds of random number generator. 68 | attr_reader :left, :right 69 | def initialize(left=nil, right=nil) 70 | @left, @right = [left, right].map do |x| 71 | if x.nil? 72 | then random 73 | else in_range(x) 74 | end 75 | end 76 | end 77 | 78 | # gen_next returns an array with length 2. The first component of 79 | # result is a random integer. The last component is a new StdGen 80 | # object as a new random number generator. See also RandomGen. 81 | def gen_next 82 | s, t = [@left, @right].map {|x| random(x) } 83 | z = ((s - t) % (@@max_bound - @@min_bound)) + @@min_bound 84 | 85 | [z, RushCheck::StdGen.new(s, t)] 86 | end 87 | 88 | # split returns an array with length 2. The components are 89 | # two new StdGen objects as two new random number generators. 90 | def split 91 | g = gen_next[1] 92 | s, t = g.left, g.right 93 | 94 | [RushCheck::StdGen.new(s + 1, t), RushCheck::StdGen.new(s, t + 1)] 95 | end 96 | 97 | # gen_range returns an array with length 2 which represents 98 | # a bound of generated random numbers. 99 | def gen_range 100 | [@@min_bound, @@max_bound] 101 | end 102 | 103 | def to_s 104 | @left.to_s + ' ' + @right.to_s 105 | end 106 | 107 | private 108 | def random(s=nil) 109 | if s.nil? then srand else srand s end 110 | rand(@@max_bound - @@min_bound) + @@min_bound 111 | end 112 | 113 | def in_range(n) 114 | (n % (@@max_bound - @@min_bound)) + @@min_bound 115 | end 116 | 117 | end 118 | 119 | 120 | require 'singleton' 121 | 122 | # TheStdGen is a singleton class to get the unique random number 123 | # generator using StdGen. TheStdGen includes ruby's Singleton module. 124 | class TheStdGen < StdGen 125 | 126 | include Singleton 127 | include RushCheck::RandomGen 128 | 129 | def initialize 130 | @gen = RushCheck::StdGen.new 131 | end 132 | 133 | def gen_next 134 | @gen, result = @gen.split 135 | 136 | result.gen_next 137 | end 138 | 139 | def split 140 | result = @gen.split 141 | @gen = @gen.gen_next[1] 142 | 143 | result 144 | end 145 | 146 | def to_s 147 | @gen.to_s 148 | end 149 | 150 | def gen_range 151 | @gen.gen_range 152 | end 153 | 154 | end 155 | 156 | 157 | # HsRandom module provides several random number function with the 158 | # functional random generator. This module is implemented Haskell\'s 159 | # System.Random library. This module assumes that the class which 160 | # includes HsRandom should have an instance method random_range to 161 | # generate a random number and a new random number generator. 162 | # It assumes also that the class which includes HsRandom should have 163 | # a class method bound to give a bound of random numbers. 164 | module HsRandom 165 | 166 | # random requires the functional random number generater (StdGen 167 | # object) and optionally requires the bound of random numbers. 168 | # It returns an array with length 2, where the first component 169 | # should be a new random number, and the last should be a new random 170 | # number generator. 171 | def random(gen, lo=nil, hi=nil) 172 | lo = bound[0] if lo.nil? 173 | hi = bound[1] if hi.nil? 174 | random_range(gen, hi, lo) if lo > hi 175 | 176 | random_range(gen, lo, hi) 177 | end 178 | 179 | # random_array requires the functional random number generater 180 | # (StdGen object). Optionally, it requires the length of results and 181 | # the bound of random numbers. This method returns different result 182 | # whether the second argument length is nil or not. When the second 183 | # argument is nil, then random_array returns a Proc which takes one 184 | # variable as an integer and return a new random value, such as an 185 | # infinite sequence of random numbers. Otherwise, the second 186 | # argument of random_array is not nil but some integer, then 187 | # random_array returns an array of random numbers with the length. 188 | def random_array(gen, len=nil, lo=nil, hi=nil) 189 | g = gen 190 | if len.nil? 191 | then 192 | Proc.new do |i| 193 | (i+1).times do 194 | v, g = random(g, lo, hi) 195 | end 196 | v 197 | end 198 | else 199 | (1..len).map do 200 | v, g = random(g, lo, hi) 201 | v 202 | end 203 | end 204 | end 205 | 206 | # random_std requires optionally the bound of random numbers. 207 | # It returns an array with length 2, where the first component 208 | # should be a new random number, and the last should be a new random 209 | # number generator. This method uses the unique standard random 210 | # generator TheStdGen. 211 | def random_std(lo=nil, hi=nil) 212 | random(RushCheck::TheStdGen.instance, lo, hi) 213 | end 214 | 215 | def random_range(gen, lo=nil, hi=nil) 216 | raise(NotImplementedError, "This method should be overrided.") 217 | end 218 | private :random_range 219 | 220 | end 221 | 222 | end 223 | -------------------------------------------------------------------------------- /lib/rushcheck/result.rb: -------------------------------------------------------------------------------- 1 | # = result.rb 2 | # This file defines results of testcase 3 | # class Result is used for RushCheck internally 4 | 5 | require 'rushcheck/property' 6 | require 'rushcheck/testable' 7 | 8 | module RushCheck 9 | 10 | class Result 11 | 12 | include RushCheck::Testable 13 | 14 | def self.nothing 15 | RushCheck::Result.new(false) 16 | end 17 | 18 | attr_reader :ok, :stamp, :arguments 19 | def initialize(ok=nil, stamp=[], arguments=[]) 20 | @ok, @stamp, @arguments = ok, stamp, arguments 21 | end 22 | 23 | def result 24 | RushCheck::Property.new(@ok, @stamp, @arguments) 25 | end 26 | alias property :result 27 | 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /lib/rushcheck/string.rb: -------------------------------------------------------------------------------- 1 | # = string.rb 2 | # an extension to String class for RushCheck 3 | # this file provides two classes String and SpecialString 4 | 5 | require 'rushcheck/arbitrary' 6 | require 'rushcheck/gen' 7 | require 'rushcheck/integer' 8 | require 'rushcheck/random' 9 | 10 | class String 11 | 12 | extend RushCheck::Arbitrary 13 | extend RushCheck::HsRandom 14 | 15 | include RushCheck::Coarbitrary 16 | 17 | @@min_range = 0 18 | @@max_range = 1024 19 | 20 | def self.arbitrary 21 | RushCheck::Gen.sized do |n| 22 | # Note the Benford's law; 23 | # http://mathworld.wolfram.com/BenfordsLaw.html 24 | # This says that (a random integer % 128).chr seems not 25 | # have /really randomness/. 26 | RushCheck::Gen.unit((0..n).map{rand(128).chr}.join) 27 | end 28 | end 29 | 30 | def self.bound 31 | [@@min_range, @@max_range] 32 | end 33 | 34 | def self.random_range(gen, lo=@@min_range, hi=@@max_range) 35 | len, g = Integer.random(gen, lo, hi) 36 | result = '' 37 | len.times do 38 | v, g = Integer.random(g, 0, 127) 39 | result += v.chr 40 | end 41 | 42 | [result. g] 43 | end 44 | 45 | def coarbitrary(g) 46 | r = g.variant(0) 47 | each_byte do |c| 48 | r = c.coarbitrary(r.variant(1)) 49 | end 50 | r 51 | end 52 | 53 | end 54 | 55 | # class SpecialString is a subclass of String. 56 | # SpecialString provides another generator which prefers 57 | # control codes and special unprinted codes than usual alphabets or numbers. 58 | # This class maybe useful to find a counter example efficiently than 59 | # using the standard generator of String. 60 | class SpecialString < String 61 | 62 | # ASCII code (see man ascii) 63 | @@alphabet = (65..90).to_a + (97..122).to_a 64 | @@control = (0..32).to_a + [177] 65 | @@number = (48..57).to_a 66 | @@special = 67 | [[33,47],[58,64],[91,96],[123,126]].inject([]) do |ary, pair| 68 | lo, hi = pair 69 | ary = ary + (lo..hi).to_a 70 | end 71 | 72 | @@frequency = { 'alphabet' => 3, 73 | 'control' => 10, 74 | 'number' => 2, 75 | 'special' => 5 } 76 | 77 | def self.arbitrary 78 | f = @@frequency 79 | frq = [] 80 | [f['alphabet'],f['control'],f['number'],f['special']].zip( 81 | [ @@alphabet, @@control, @@number, @@special]) do 82 | |weight, table| 83 | gen = RushCheck::Gen.oneof(table.map {|n| RushCheck::Gen.unit(n.chr)}) 84 | frq << [weight, gen] 85 | end 86 | 87 | RushCheck::Gen.sized do |m| 88 | RushCheck::Gen.choose(0, m).bind do |len| 89 | RushCheck::Gen.new do |n, r| 90 | r2 = r 91 | (1..len).map do 92 | r1, r2 = r2.split 93 | RushCheck::Gen.frequency(frq).value(n, r1) 94 | end.join 95 | end 96 | end 97 | end 98 | end 99 | 100 | end 101 | -------------------------------------------------------------------------------- /lib/rushcheck/testable.rb: -------------------------------------------------------------------------------- 1 | # = testable.rb 2 | # this provides an abstract interface of Testable 3 | # See also QuickCheck in Haskell 4 | 5 | require 'rushcheck/config' 6 | require 'rushcheck/random' 7 | require 'rushcheck/testresult' 8 | 9 | module RushCheck 10 | 11 | module Testable 12 | 13 | def property 14 | raise(NotImplementedError, "This method should be overrided.") 15 | end 16 | 17 | def check(config=RushCheck::Config.new) 18 | config.tests(property.gen, RushCheck::TheStdGen.instance) 19 | end 20 | alias quick_check :check 21 | alias quickcheck :check 22 | 23 | def classify(name) 24 | yield ? label(name) : property 25 | end 26 | 27 | def imply 28 | yield ? property : RushCheck::Result.nothing.result 29 | end 30 | 31 | def label(s) 32 | RushCheck::Property.new(property.gen.fmap {|res| res.stamp << s.to_s; res }) 33 | end 34 | alias collect :label 35 | 36 | def run(opts) 37 | RushCheck::Config.batch(opts.ntests, opts.debug?). 38 | tests_batch(property, RushCheck::StdGen.new(0)) 39 | end 40 | 41 | def rjustify(n, s) 42 | ' ' * [0, n - s.length].max + s 43 | end 44 | private :rjustify 45 | 46 | def try_test(ts) 47 | ntests = 1 48 | count = 0 49 | others = [] 50 | if ! ts.empty? 51 | ts.each do |t| 52 | begin 53 | r = t.call(opts) 54 | case r 55 | when RushCheck::TestOk 56 | puts "." 57 | ntests += 1 58 | count += r.ntests 59 | when RushCheck::TestExausted 60 | puts "?" 61 | ntests += 1 62 | count += r.ntests 63 | when RushCheck::TestFailed 64 | puts "#" 65 | ntests += 1 66 | others << [r.results, ntests, r.ntests] 67 | count += r.ntests 68 | else 69 | raise(RuntimeError, "RushCheck: illegal result") 70 | end 71 | rescue 72 | puts "*" 73 | ntests += 1 74 | next 75 | end 76 | end 77 | end 78 | print(rjustify([0, 35-ntests].max, " (") + count.to_s + ")\n") 79 | others 80 | end 81 | private :try_test 82 | 83 | def final_run(f, n, no, name) 84 | puts 85 | puts " ** test " + n.to_s + " of " + name + " failed with the binding(s)" 86 | f.each do |v| 87 | puts " ** " + v.to_s 88 | end 89 | puts 90 | end 91 | private :final_run 92 | 93 | def run_tests(name, opts, tests) 94 | print(rjustify(25, name) + " : ") 95 | f = try_test(tests) 96 | f.each { |f, n, no| final_run(f, n, no, name) } 97 | nil 98 | end 99 | 100 | def silent_check 101 | check(RushCheck::Config.silent) 102 | end 103 | 104 | def test 105 | check(RushCheck::Config.verbose) 106 | end 107 | alias verbose_check :test 108 | 109 | def testcase 110 | Proc.new {|opts| run(opts)} 111 | end 112 | 113 | def trivial 114 | classify('trivial') { yield } 115 | end 116 | end 117 | 118 | end 119 | 120 | -------------------------------------------------------------------------------- /lib/rushcheck/testoptions.rb: -------------------------------------------------------------------------------- 1 | # = testoptions.rb 2 | # define class TestOptions for a batch process for RushCheck 3 | 4 | module RushCheck 5 | 6 | class TestOptions 7 | 8 | attr_reader :ntests, :length 9 | def initialize(ntests=100, length=1, debug=false) 10 | @ntests, @length, @debug = ntests, length, debug 11 | end 12 | 13 | def debug? 14 | @debug 15 | end 16 | 17 | end 18 | 19 | end 20 | 21 | -------------------------------------------------------------------------------- /lib/rushcheck/testresult.rb: -------------------------------------------------------------------------------- 1 | # = testresult.rb 2 | # 3 | 4 | module RushCheck 5 | 6 | class TestResult 7 | attr_reader :message, :ntests, :stamps 8 | def initialize(message, ntests, stamps) 9 | @message, @ntest, @stamps = message, ntests, stamps 10 | end 11 | end 12 | 13 | class TestOk < TestResult; end 14 | 15 | class TestExausted < TestResult; end 16 | 17 | class TestFailed 18 | attr_reader :results, :ntests 19 | def initialize(results, ntests) 20 | @results, @ntests = results, ntests 21 | end 22 | end 23 | 24 | end 25 | 26 | -------------------------------------------------------------------------------- /lib/rushcheck/version.rb: -------------------------------------------------------------------------------- 1 | # version.rb 2 | # 3 | 4 | module RushCheck 5 | 6 | module Version 7 | 8 | MAJOR = 0 9 | MINOR = 9 10 | PATCH = 0 11 | 12 | STRING = [MAJOR, MINOR, PATCH].compact.join('.') 13 | 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /rushcheck.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{rushcheck} 8 | s.version = "0.9.0" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Daisuke IKEGAMI"] 12 | s.date = %q{2011-03-28} 13 | s.description = %q{Specification based random testing framework like QuickCheck in Haskell} 14 | s.email = %q{ikegami.da@gmail.com} 15 | s.extra_rdoc_files = [ 16 | "LICENSE.txt", 17 | "README.rdoc" 18 | ] 19 | s.files = [ 20 | "Gemfile", 21 | "Gemfile.lock", 22 | "LICENSE.txt", 23 | "README.rdoc", 24 | "Rakefile", 25 | "data/doc/policy.txt", 26 | "data/doc/rushcheck.thtml", 27 | "data/doc/spec.txt", 28 | "data/examples/candy.rb", 29 | "data/examples/printf.rb", 30 | "data/examples/proc.rb", 31 | "data/examples/roguetile.rb", 32 | "data/examples/rspec/stack.rb", 33 | "data/examples/rspec/stack_spec.rb", 34 | "data/examples/sample.rb", 35 | "lib/rushcheck.rb", 36 | "lib/rushcheck/arbitrary.rb", 37 | "lib/rushcheck/array.rb", 38 | "lib/rushcheck/assertion.rb", 39 | "lib/rushcheck/bool.rb", 40 | "lib/rushcheck/claim.rb", 41 | "lib/rushcheck/config.rb", 42 | "lib/rushcheck/float.rb", 43 | "lib/rushcheck/gen.rb", 44 | "lib/rushcheck/guard.rb", 45 | "lib/rushcheck/hash.rb", 46 | "lib/rushcheck/integer.rb", 47 | "lib/rushcheck/proc.rb", 48 | "lib/rushcheck/property.rb", 49 | "lib/rushcheck/random.rb", 50 | "lib/rushcheck/result.rb", 51 | "lib/rushcheck/string.rb", 52 | "lib/rushcheck/testable.rb", 53 | "lib/rushcheck/testoptions.rb", 54 | "lib/rushcheck/testresult.rb", 55 | "lib/rushcheck/version.rb", 56 | "test/helper.rb", 57 | "test/prelude.rb", 58 | "test/spec_array.rb", 59 | "test/spec_assertion.rb", 60 | "test/spec_gen.rb", 61 | "test/spec_random.rb", 62 | "test/test_arbitrary.rb", 63 | "test/test_array.rb", 64 | "test/test_assertion.rb", 65 | "test/test_rushcheck.rb" 66 | ] 67 | s.homepage = %q{http://github.com/IKEGAMIDaisuke/rushcheck} 68 | s.licenses = ["MIT"] 69 | s.require_paths = ["lib"] 70 | s.rubygems_version = %q{1.3.7} 71 | s.summary = %q{Random testing framework for Ruby} 72 | s.test_files = [ 73 | "test/helper.rb", 74 | "test/prelude.rb", 75 | "test/spec_array.rb", 76 | "test/spec_assertion.rb", 77 | "test/spec_gen.rb", 78 | "test/spec_random.rb", 79 | "test/test_arbitrary.rb", 80 | "test/test_array.rb", 81 | "test/test_assertion.rb", 82 | "test/test_rushcheck.rb" 83 | ] 84 | 85 | if s.respond_to? :specification_version then 86 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 87 | s.specification_version = 3 88 | 89 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 90 | s.add_development_dependency(%q, [">= 0"]) 91 | s.add_development_dependency(%q, ["~> 1.0.0"]) 92 | s.add_development_dependency(%q, ["~> 1.5.2"]) 93 | s.add_development_dependency(%q, [">= 0"]) 94 | else 95 | s.add_dependency(%q, [">= 0"]) 96 | s.add_dependency(%q, ["~> 1.0.0"]) 97 | s.add_dependency(%q, ["~> 1.5.2"]) 98 | s.add_dependency(%q, [">= 0"]) 99 | end 100 | else 101 | s.add_dependency(%q, [">= 0"]) 102 | s.add_dependency(%q, ["~> 1.0.0"]) 103 | s.add_dependency(%q, ["~> 1.5.2"]) 104 | s.add_dependency(%q, [">= 0"]) 105 | end 106 | end 107 | 108 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | begin 4 | Bundler.setup(:default, :development) 5 | rescue Bundler::BundlerError => e 6 | $stderr.puts e.message 7 | $stderr.puts "Run `bundle install` to install missing gems" 8 | exit e.status_code 9 | end 10 | require 'test/unit' 11 | require 'shoulda' 12 | 13 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 14 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 15 | require 'rushcheck' 16 | 17 | class Test::Unit::TestCase 18 | end 19 | -------------------------------------------------------------------------------- /test/prelude.rb: -------------------------------------------------------------------------------- 1 | $: << File.dirname(__FILE__) + "/../lib" 2 | require 'rushcheck' 3 | 4 | def for_all(*cs, &f) 5 | RushCheck::Claim.new(*cs, &f).check.should_equal true 6 | end 7 | -------------------------------------------------------------------------------- /test/spec_array.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/prelude.rb' 2 | 3 | module RushCheck 4 | class Gen 5 | extend RushCheck::Arbitrary 6 | def self.arbitrary 7 | Integer.arbitrary.fmap {|x| self.unit(x)} 8 | end 9 | end 10 | end 11 | 12 | class MyRandomArray < RandomArray; end 13 | class MyRandomProc < RandomProc; end 14 | context 'type checking for a subclass of RandomArray' do 15 | 16 | specify 'self.arbitrary should return Gen object' do 17 | MyRandomArray.arbitrary.should_be_an_instance_of RushCheck::Gen 18 | end 19 | 20 | specify 'coarbitrary should return Gen object' do 21 | for_all(RushCheck::Gen) do |gen| 22 | MyRandomArray.new.coarbitrary(gen).should_be_an_instance_of RushCheck::Gen 23 | end 24 | end 25 | 26 | end 27 | 28 | context 'properties of self.arbitrary' do 29 | 30 | setup do 31 | MyRandomArray.set_pattern(Integer) {|ary, i| Integer} 32 | @stdgen = RushCheck::TheStdGen.instance 33 | end 34 | 35 | specify 'returned object is Gen of Array without set_pattern' do 36 | for_all(Integer) do |n| 37 | MyRandomArray.arbitrary.value(n, @stdgen).should_be_an_instance_of Array 38 | end 39 | end 40 | 41 | specify 'returned object is Gen of Array with set_pattern - base:Class, ind:Class' do 42 | MyRandomArray.set_pattern(Integer) {|ary, i| Integer} 43 | for_all(Integer) do |n| 44 | MyRandomArray.arbitrary.value(n, @stdgen).each do |x| 45 | x.should_be_a_kind_of Integer 46 | end 47 | end 48 | end 49 | 50 | specify 'returned object is Gen of Array with set_pattern - base:Array, ind:Class' do 51 | MyRandomArray.set_pattern([Integer]) {|ary, i| Integer} 52 | for_all(Integer) do |n| 53 | ary = MyRandomArray.arbitrary.value(n, @stdgen) 54 | ary.should_be_a_kind_of Array 55 | end 56 | end 57 | 58 | specify 'returned object is Gen of Array with set_pattern - base:Array, ind:Class' do 59 | MyRandomArray.set_pattern([Integer]) {|ary, i| Integer} 60 | for_all(Integer) do |n| 61 | ary = MyRandomArray.arbitrary.value(n, @stdgen) 62 | RushCheck::guard {! ary.empty?} 63 | q = ary.first 64 | q.should_be_a_kind_of Array 65 | q.first.should_be_a_kind_of Integer 66 | end 67 | end 68 | 69 | specify 'returned object is Gen of Array with set_pattern - base:Class, ind:Array' do 70 | MyRandomArray.set_pattern(Integer) {|ary, i| [Integer]} 71 | for_all(Integer) do |n| 72 | ary = MyRandomArray.arbitrary.value(n, @stdgen) 73 | RushCheck::guard {! ary.empty?} 74 | q = ary.first 75 | q.should_be_a_kind_of Integer 76 | ary[1..(-1)].each do |x| 77 | x.should_be_a_kind_of Array 78 | x.first.should_be_a_kind_of Integer 79 | end 80 | end 81 | end 82 | 83 | end 84 | 85 | context 'properties of coarbitrary' do 86 | 87 | specify 'returned object is Gen of Integer when coarbitrary takes Gen of Integer' do 88 | for_all(RushCheck::Gen, Integer) do |gen, n| 89 | MyRandomArray.new.coarbitrary(gen).value(n, @stdgen).should_be_a_kind_of Integer 90 | end 91 | end 92 | 93 | end 94 | 95 | -------------------------------------------------------------------------------- /test/spec_assertion.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/prelude.rb' 2 | 3 | context 'properties of Assertion.new.property' do 4 | specify 'no argument with empty block should be an instance of Property' do 5 | RushCheck::Assertion.new(){}.property.should_be_an_instance_of RushCheck::Property 6 | end 7 | 8 | specify 'some arguments with empty block should be an instance of Property' do 9 | RushCheck::Assertion.new(Integer){}.property.should_be_an_instance_of RushCheck::Property 10 | RushCheck::Assertion.new(Integer, String){}.property.should_be_an_instance_of RushCheck::Property 11 | end 12 | end 13 | 14 | context 'properties of Assertion.new() {...}.check' do 15 | specify 'checking true should be true' do 16 | RushCheck::Assertion.new(){true}.check.should_be true 17 | end 18 | 19 | specify 'checking false should be raised' do 20 | RushCheck::Assertion.new(){false}.check.should_be_raise 21 | end 22 | end 23 | 24 | context 'properties of Assertion.new(some classes) {...}.check' do 25 | specify 'checking true should be true' do 26 | RushCheck::Assertion.new(Integer){|x| true}.check.should_be true 27 | RushCheck::Assertion.new(Integer, String){|x, y| true}.check.should_be true 28 | end 29 | 30 | specify 'checking false should be raised' do 31 | RushCheck::Assertion.new(Integer){|x| false}.check.should_be_raise 32 | RushCheck::Assertion.new(Integer, String){|x, y| false}.check.should_be_raise 33 | end 34 | end 35 | 36 | -------------------------------------------------------------------------------- /test/spec_gen.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/prelude.rb' 2 | 3 | module RushCheck 4 | class Gen 5 | extend RushCheck::Arbitrary 6 | def self.arbitrary 7 | Integer.arbitrary.fmap {|x| self.unit x} 8 | end 9 | end 10 | end 11 | 12 | class MyRandomArray < NonEmptyRandomArray; end 13 | class MyRandomProc < RandomProc; end 14 | context 'type checking for Gen' do 15 | 16 | setup do 17 | @stdgen = RushCheck::TheStdGen.instance 18 | end 19 | 20 | specify 'Gen.choose should be Random a => (a, a) -> Gen a' do 21 | for_all(Integer, Integer) do |lo, hi| 22 | RushCheck::Gen.choose(lo, hi).should_be_an_instance_of RushCheck::Gen 23 | end 24 | end 25 | 26 | specify 'Gen.elements should be [a] -> Gen a' do 27 | MyRandomArray.set_pattern(Integer) {|ary, i| Integer} 28 | for_all(MyRandomArray, Integer) do |xs, n| 29 | g = RushCheck::Gen.elements(xs) 30 | g.should_be_an_instance_of RushCheck::Gen 31 | g.value(n, @stdgen).should_be_a_kind_of Integer 32 | end 33 | end 34 | 35 | specify 'Gen.frequency should be [(Int, Gen a)] -> Gen a' do 36 | MyRandomArray.set_pattern([PositiveInteger, RushCheck::Gen]) do 37 | |ary, i| [PositiveInteger, RushCheck::Gen] end 38 | for_all(MyRandomArray, Integer) do |xs, n| 39 | g = RushCheck::Gen.frequency(xs) 40 | g.should_be_an_instance_of RushCheck::Gen 41 | g.value(n, @stdgen).should_be_a_kind_of Integer 42 | end 43 | end 44 | 45 | specify 'Gen.oneof should be [Gen a] -> Gen a' do 46 | MyRandomArray.set_pattern(RushCheck::Gen) {|ary, i| RushCheck::Gen} 47 | for_all(MyRandomArray, Integer) do |xs, n| 48 | g = RushCheck::Gen.oneof(xs) 49 | g.should_be_an_instance_of RushCheck::Gen 50 | g.value(n, @stdgen).should_be_a_kind_of Integer 51 | end 52 | end 53 | 54 | specify 'Gen.promote should be (a -> Gen b) -> Gen (a -> b)' do 55 | MyRandomProc.set_pattern([Integer], [RushCheck::Gen]) 56 | for_all(MyRandomProc, Integer, Integer) do |proc, n, x| 57 | g = RushCheck::Gen.promote(&proc) 58 | g.should_be_an_instance_of RushCheck::Gen 59 | g.value(n, @stdgen).should_be_a_kind_of Proc 60 | g.value(n, @stdgen).call(x).should_be_a_kind_of Integer 61 | end 62 | end 63 | 64 | specify 'Gen.rand should be Gen StdGen' do 65 | for_all(Integer) do |gen, n| 66 | g = RushCheck::Gen.rand 67 | g.should_be_an_instance_of RushCheck::Gen 68 | g.value(n, @stdgen).should_be_a_kind_of RushCheck::StdGen 69 | end 70 | end 71 | 72 | specify 'Gen.sized should be (Int -> Gen a) -> Gen a' do 73 | MyRandomProc.set_pattern([Integer], [RushCheck::Gen]) 74 | for_all(MyRandomProc, Integer) do |proc, n| 75 | g = RushCheck::Gen.sized(&proc) 76 | g.should_be_an_instance_of RushCheck::Gen 77 | g.value(n, @stdgen).should_be_a_kind_of Integer 78 | end 79 | end 80 | 81 | specify 'Gen.unit should be a -> Gen a' do 82 | for_all(Integer, Integer) do |x, n| 83 | g = RushCheck::Gen.unit(x) 84 | g.should_be_an_instance_of RushCheck::Gen 85 | g.value(n, @stdgen).should_be_a_kind_of Integer 86 | end 87 | end 88 | 89 | specify 'Gen.vector should be Arbitrary a => Int -> Gen [a]' do 90 | for_all(PositiveInteger, Integer) do |len, n| 91 | g = RushCheck::Gen.vector(Integer, len) 92 | g.should_be_an_instance_of RushCheck::Gen 93 | ary = g.value(n, @stdgen) 94 | ary.should_be_a_kind_of Array 95 | ary.each {|i| i.should_be_a_kind_of Integer} 96 | end 97 | end 98 | 99 | specify 'bind should be Gen a |-> (a -> Gen b) -> Gen b' do 100 | MyRandomProc.set_pattern([Integer], [RushCheck::Gen]) 101 | for_all(RushCheck::Gen, MyRandomProc, Integer) do |g, proc, n| 102 | h = g.bind(&proc) 103 | h.should_be_an_instance_of RushCheck::Gen 104 | h.value(n, @stdgen).should_be_a_kind_of Integer 105 | end 106 | end 107 | 108 | specify 'value should be Gen a |-> Integer -> StdGen -> a' do 109 | for_all(RushCheck::Gen, Integer) do |g, n| 110 | g.value(n, @stdgen).should_be_a_kind_of Integer 111 | end 112 | end 113 | 114 | specify 'fmap should be Gen a |-> (a -> b) -> Gen b' do 115 | MyRandomProc.set_pattern([Integer], [String]) 116 | for_all(RushCheck::Gen, MyRandomProc, Integer) do |g, proc, n| 117 | h = g.fmap(&proc) 118 | h.should_be_an_instance_of RushCheck::Gen 119 | h.value(n, @stdgen).should_be_a_kind_of String 120 | end 121 | end 122 | 123 | specify 'forall should be (Show a, Testable b) => Gen a |-> (a -> b) -> Gen Result' do 124 | MyRandomProc.set_pattern([Integer], [TrueClass]) 125 | for_all(RushCheck::Gen, MyRandomProc, Integer) do |g, proc, n| 126 | h = g.forall(&proc) 127 | h.should_be_a_kind_of RushCheck::Gen 128 | h.value(n, @stdgen).should_be_a_kind_of RushCheck::Result 129 | end 130 | end 131 | 132 | specify 'generate should be Gen a |-> Int -> StdGen -> a' do 133 | for_all(RushCheck::Gen, Integer) do |g, x| 134 | g.generate(x, @stdgen).should_be_a_kind_of Integer 135 | end 136 | end 137 | 138 | specify 'resize should be Gen a |-> Int -> Gen a' do 139 | for_all(RushCheck::Gen, Integer, Integer) do |g, x, n| 140 | h = g.resize(x) 141 | h.should_be_an_instance_of RushCheck::Gen 142 | h.value(n, @stdgen).should_be_a_kind_of Integer 143 | end 144 | end 145 | 146 | specify 'variant should be Gen a |-> Int -> Gen a' do 147 | for_all(RushCheck::Gen, Integer, Integer) do |g, x, n| 148 | h = g.variant(x) 149 | h.should_be_an_instance_of RushCheck::Gen 150 | h.value(n, @stdgen).should_be_a_kind_of Integer 151 | end 152 | end 153 | 154 | end 155 | -------------------------------------------------------------------------------- /test/spec_random.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/prelude.rb' 2 | 3 | require 'rushcheck/random' 4 | 5 | context 'class StdGen' do 6 | 7 | specify 'left and right of StdGen.new should be an random integer in the range' do 8 | sg = RushCheck::StdGen.new 9 | left, right = sg.left, sg.right 10 | [left, right].each do |n| 11 | n.should_be_a_kind_of Integer 12 | end 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /test/test_arbitrary.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rushcheck/arbitrary' 3 | 4 | class TC_Arbitrary < Test::Unit::TestCase 5 | 6 | class TC_Arbitrary_Foo 7 | extend RushCheck::Arbitrary 8 | end 9 | 10 | def setup 11 | end 12 | 13 | def teardown 14 | end 15 | 16 | def test_arbitrary 17 | assert_raise(NotImplementedError) { TC_Arbitrary_Foo.arbitrary } 18 | end 19 | 20 | end 21 | 22 | class TC_Coarbitrary < Test::Unit::TestCase 23 | 24 | include RushCheck::Coarbitrary 25 | 26 | def setup 27 | end 28 | 29 | def teardown 30 | end 31 | 32 | def test_coarbitrary 33 | assert_raise(NotImplementedError) { coarbitrary(nil) } 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /test/test_array.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rushcheck/array' 3 | 4 | class TC_RandomArray < Test::Unit::TestCase 5 | 6 | class IntegerRandomArray < NonEmptyRandomArray; end 7 | class StringRandomArray < NonEmptyRandomArray; end 8 | 9 | def setup 10 | IntegerRandomArray.set_pattern {|i| Integer} 11 | StringRandomArray.set_pattern {|i| String} 12 | end 13 | 14 | def teardown 15 | end 16 | 17 | def test_random_array_nonempty 18 | assert_equal true, 19 | RushCheck::Assertion.new(IntegerRandomArray) { |xs| 20 | ! xs.empty? 21 | }.check 22 | end 23 | 24 | def test_random_array_class_integer 25 | results = [] 26 | assert_equal true, 27 | RushCheck::Assertion.new(IntegerRandomArray) { |xs| 28 | # skips if xs is empty 29 | xs.each { |x| 30 | # x.class should be Fixnum or merely Bignum 31 | results << (Integer == x.class.superclass) 32 | } 33 | results.all? {|r| r == true} 34 | }.check 35 | end 36 | 37 | def test_random_array_class_string 38 | results = [] 39 | assert_equal true, 40 | RushCheck::Assertion.new(StringRandomArray) { |xs| 41 | # skips if xs is empty 42 | xs.each { |x| 43 | # x.class should be String 44 | results << (String == x.class) 45 | } 46 | results.all? {|r| r == true} 47 | }.check 48 | end 49 | 50 | def test_arbitrary 51 | assert_instance_of(RushCheck::Gen, 52 | IntegerRandomArray.arbitrary) 53 | end 54 | 55 | def test_coarbitrary 56 | g = RushCheck::Gen.unit(0) 57 | assert_instance_of(RushCheck::Gen, 58 | IntegerRandomArray.new.coarbitrary(g)) 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /test/test_assertion.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'rushcheck/error' 3 | require 'rushcheck/assertion' 4 | 5 | class TC_Assertion < Test::Unit::TestCase 6 | 7 | def setup 8 | end 9 | 10 | def teardown 11 | end 12 | 13 | def test_assertion_illegal 14 | assert_raise(RushCheck::InternalError::RushCheckError) { 15 | RushCheck::Assertion.new(Integer) { |x| nil }.check 16 | } 17 | end 18 | 19 | def test_assertion_failed_not_class 20 | assert_raise(RushCheck::InternalError::RushCheckError) { 21 | RushCheck::Assertion.new(0) { |x| true }.check 22 | } 23 | end 24 | 25 | def test_assertion_failed_invalid_vars 26 | assert_raise(RushCheck::InternalError::RushCheckError) { 27 | RushCheck::Assertion.new(Integer) { 28 | |x, y| 29 | true 30 | }.check 31 | } 32 | end 33 | 34 | def test_assertion_raise_trivial 35 | assert_raise(RushCheck::InternalError::RushCheckError) { 36 | RushCheck::Assertion.new { true }.check 37 | } 38 | end 39 | 40 | def test_assertion_nothing_raised 41 | assert_nothing_raised { 42 | RushCheck::Assertion.new(Integer, String) { 43 | |x, y| 44 | true 45 | }.check 46 | } 47 | end 48 | 49 | def test_assertion_guard_passed 50 | assert_nothing_raised { 51 | RushCheck::Assertion.new(Integer) { 52 | |x| 53 | RushCheck::guard { true } 54 | true 55 | }.check 56 | } 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /test/test_rushcheck.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | # class TestRushcheck < Test::Unit::TestCase 4 | # should "probably rename this file and start testing for real" do 5 | # flunk "hey buddy, you should probably rename this file and start testing for real" 6 | # end 7 | # end 8 | --------------------------------------------------------------------------------