├── .autotest ├── History.txt ├── Manifest.txt ├── README.txt ├── Rakefile ├── articles ├── Article.css └── how_to_use_zentest.txt ├── bin ├── multigem ├── multiruby ├── unit_diff └── zentest ├── example.txt ├── example1.rb ├── example2.rb ├── lib ├── focus.rb ├── functional_test_matrix.rb ├── unit_diff.rb ├── zentest.rb └── zentest_mapping.rb └── test ├── test_focus.rb ├── test_unit_diff.rb ├── test_zentest.rb └── test_zentest_mapping.rb /.autotest: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require 'autotest/restart' 4 | 5 | Autotest.add_hook :initialize do |at| 6 | at.add_exception 'coverage' 7 | at.add_exception 'coverage.info' 8 | 9 | at.libs << ':../../minitest/dev/lib' 10 | 11 | %w(TestZenTest).each do |klass| 12 | at.extra_class_map[klass] = "test/test_zentest.rb" 13 | end 14 | end 15 | 16 | Autotest.add_hook :all_good do |at| 17 | system "rake rcov_info" 18 | end if ENV['RCOV'] 19 | 20 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | === 4.12.2 / 2024-07-02 2 | 3 | * 1 bug fix: 4 | 5 | * Fix errors created when string literals are frozen. 6 | 7 | === 4.12.1 / 2022-01-17 8 | 9 | * 1 bug fix: 10 | 11 | * ruby 3.1: Fixed YAML.load switching to safe_load. 12 | 13 | === 4.12.0 / 2019-09-22 14 | 15 | * 3 major enhancements: 16 | 17 | * Deleted autotest from project. Use minitest-autotest instead. 18 | * Removed multiruby_setup. Use ruby-install or ruby-build or install your own. 19 | * Update multiruby to use ~/.rubies (default for ruby-install). 20 | 21 | * 4 minor enhancements: 22 | 23 | * Find and use the multiruby next to multigem. 24 | * multiruby ignores GEM_HOME and GEM_PATH (to allow multigem to work). 25 | * multiruby respects global `multiruby_skip` entries in `~/.hoerc`. 26 | * multiruby sorts versions properly so glob ordering is consistent. 27 | 28 | * 1 bug fix: 29 | 30 | * Removed hacks for rbx because nobody uses rbx. 31 | 32 | === 4.11.2 / 2019-01-02 33 | 34 | * 1 bug fix: 35 | 36 | * Removed unneeded and out-of-date require_rubygems_version. 37 | 38 | === 4.11.1 / 2016-06-13 39 | 40 | * 1 minor enhancement: 41 | 42 | * Expand autotest to load 'autotest/discover*' for discovery. 43 | 44 | === 4.11.0 / 2014-09-26 45 | 46 | * 1 minor enhancement: 47 | 48 | * Rearranged died hook so user can bypass default. (bhenderson) 49 | 50 | * 1 bug fix: 51 | 52 | * Fixed rubyforge urls. 53 | 54 | === 4.10.1 / 2014-07-07 55 | 56 | * 2 bug fixes: 57 | 58 | * Change require to minitest/autorun for non-test/unit style 59 | * Change the way files are gathered to avoid StackOverflow exception if you have a TOOON of files. (joshwand) 60 | 61 | === 4.10.0 / 2014-04-23 62 | 63 | * 2 minor enhancements: 64 | 65 | * Added test_mappings accessor methods (ivar already there). 66 | * Added test_prefix to allow you to run code ahead of test loading. (default: gem minitest) 67 | 68 | === 4.9.5 / 2013-11-01 69 | 70 | * 1 bug fix: 71 | 72 | * Updated require_rubygems_version AGAIN... going too fast. (zzak) 73 | 74 | === 4.9.4 / 2013-09-20 75 | 76 | * 2 bug fixes: 77 | 78 | * Expand required rubygems version to be < 2.2 79 | * Fix parsing of minitest 5 output. 80 | 81 | === 4.9.3 / 2013-08-12 82 | 83 | * 1 minor enhancement: 84 | 85 | * Added --debug option. Probably need to add more to help bug reports. 86 | 87 | * 2 bug fixes: 88 | 89 | * Fixed completed_re and failed_results_re to match minitest 5 (and still mt 4). 90 | * Fixed handle_results to deal with minitest 5 output (eg ClassName#method_name). 91 | 92 | === 4.9.2 / 2013-05-29 93 | 94 | * 2 minor enhancements: 95 | 96 | * Added autotest-suffix description to readme. (blowmage) 97 | * zentest now outputs test classes for minitest 5 98 | 99 | === 4.9.1 / 2013-04-18 100 | 101 | * 2 minor enhancements: 102 | 103 | * Make banner more explicit about correct test file location. (rue) 104 | * Switched to #failed for calculating red/green hook. (bhenderson) 105 | 106 | === 4.9.0 / 2013-02-07 107 | 108 | * 1 minor enhancement: 109 | 110 | * Allow multiruby installs to be done entirely by symlink, bypassing build 111 | 112 | * 1 bug fix: 113 | 114 | * Fixes for maglev. 115 | 116 | === 4.8.4 / 2013-01-22 117 | 118 | * 2 minor enhancements: 119 | 120 | * Set required rubygems version to be >= 1.8 and < 2.1. (sanemat) 121 | * multiruby now just builds main, which skips rdoc and other stuff 122 | 123 | * 1 bug fix: 124 | 125 | * 1.9 proofed Module#focus 126 | 127 | === 4.8.3 / 2012-12-06 128 | 129 | * 1 bug fix: 130 | 131 | * 2.0: Minor tweak to test to get it to pass on 2.0 132 | 133 | === 4.8.2 / 2012-07-26 134 | 135 | * 1 bug fix: 136 | 137 | * Fixed 1.9+ warnings in multiruby. (bhenderson) 138 | 139 | === 4.8.1 / 2012-06-01 140 | 141 | * 1 bug fix: 142 | 143 | * Fixed 1.9 bug caused by differences between Hash#find_all and Hash#select. (semaperepelitsa) 144 | 145 | === 4.8.0 / 2012-05-04 146 | 147 | * 1 minor enhancement: 148 | 149 | * Added Minitest generation to zentest (use -t to generate for test/unit) 150 | 151 | * 1 bug fix: 152 | 153 | * Fixes and clarifications to Autotest#find_file. (hugh sasse) 154 | 155 | === 4.7.0 / 2012-03-15 156 | 157 | * 2 minor enhancements: 158 | 159 | * autotest/timestamp now uses ran_command hook. (bhenderson) 160 | * run_command hook is now passed cmd. (bhenderson) 161 | 162 | * 1 bug fix: 163 | 164 | * Fixed run_command hook to only trigger when there are test files to run. (bhenderson) 165 | 166 | === 4.6.2 / 2011-08-24 167 | 168 | * 1 minor enhancement: 169 | 170 | * Added Autotest Tips section to Readme 171 | 172 | * 1 bug fix: 173 | 174 | * Fixed mri 1.9.3 change to Find.find with autotest/restart and missing files. 175 | 176 | === 4.6.1 / 2011-08-11 177 | 178 | * 3 bug fixes: 179 | 180 | * Fix for option flags and unhandled error warning in autotest. (dbackeus) 181 | * Fix option w/ args handling and restart by storing ARGV in options[:args] 182 | * Fixed autotest --rc option handling. (simplybusiness) 183 | 184 | === 4.6.0 / 2011-07-22 185 | 186 | * 6 minor enhancements: 187 | 188 | * Added -p (plain diff) and made -u (unified diff) the default for unit_diff. 189 | * Added ./tmp to default exclusions. 190 | * Autotest defaults unit_diff to nil now, to allow minitest's enhanced assert_equal to shine. 191 | * Autotest will raise if the :died handler doesn't handle the exception. (ralfebert) 192 | * Dropped 1.8.6 as a default tag/branch to build in multiruby. 193 | * autotest can now be automatically 'narrowed' on the command-line. 194 | * eg: `autotest lib test/test_blah.rb` 195 | 196 | * 7 bug fixes: 197 | 198 | * Believe it or not... but some ppl use zentest. Fixed nested class vs module bug 199 | * Fix to turn on jruby's support for ObjectSpace. (stepheneb) 200 | * Fixed a rubygems deprecation in autotest 201 | * Fixed unit_diff scanning for minitest output. 202 | * Normalized shebangs to fix problems on windows (luis) 203 | * autotest/isolate.rb sets GEM_HOME as well as GEM_PATH. 204 | * Fixed 1.9.3 warnings. 205 | 206 | === 4.5.0 / 2011-02-18 207 | 208 | * 6 minor enhancements: 209 | 210 | * Added autotest -w flag to turn on warnings. Turned off warnings by default. 211 | * Added autotest/preload.rb to deal with rails being egregiously slow. 212 | * Added child process handling/cleanup on signals. 213 | * Added postinitialize hook. 214 | * Improved restart mechanism to include all flags. 215 | * Refactored restart plugin to Autotest#restart. 216 | 217 | * 5 bug fixes: 218 | 219 | * Added sigquit handler to restart app straight up. 220 | * Fixed autotest/isolate so it works 221 | * Fixed parse_options to take args array (default ARGV) and to be non-destructive. 222 | * Strip ascii color to avoid false positives. (graemeworthy) 223 | * Use RbConfig to remove warning 224 | 225 | === 4.4.2 / 2010-12-10 226 | 227 | * 2 bug fixes: 228 | 229 | * Added a temporary hack to deal with rspec(2?) discovery hacks. 230 | * Fixed windoze detection for unit_diff (thyresias) 231 | 232 | === 4.4.1 / 2010-12-01 233 | 234 | * 12 minor enhancements: 235 | 236 | * Merged in most things from the autotest gem: 237 | * Added --no-full-after-failed. (grosser) 238 | * Added --rc path-to-dot-autotest. (grosser) 239 | * Added --style autotest-style. (grosser) 240 | * Added clarification comments. (grosser) 241 | * Added rake task descriptions. (grosser) 242 | * Switched to optparse. (grosser) 243 | * Switched windoze detection. (grosser, tenderlove) 244 | * Did not merge test parallelization. It should be a plugin. Awaiting patch. 245 | * Added autotest/bundler plugin. 246 | * Added autotest/isolate plugin. 247 | * Added capture of skips (for minitest) from result line. 248 | * Added focus_re to focus.rb. 249 | * Added latest_results hash. 250 | * Cleaned up unit_diff matcher by using any? 251 | * Enhanced the help/usage for autotest and zentest. (hugh sasse) 252 | * Refactored autotest runner discovery. 253 | * Refactored ruby_cmd to allow plugins to affect ruby execution. 254 | * Removed befuddling override of Dir.[] from 2007. *shrug* 255 | * Removed deny method in favor of aliasing refute. 256 | 257 | * 2 bug fixes: 258 | 259 | * 1.9 shadowed variable fix 260 | * autotest/restart now passes ARGV back through to exec. yay! 261 | 262 | === 4.4.0 / 2010-09-01 263 | 264 | * 1 major enhancement: 265 | 266 | * Removed git building. I'm dropping direct support for rubinius. 267 | 268 | * 3 minor enhancements: 269 | 270 | * Added multiruby mri:list:x.y.z command so you can see what's available. 271 | * Enabled installing specific patch versions of ruby. 272 | * multiruby rubygems:update now forces a build to be less confusing. 273 | 274 | * 1 bug fix: 275 | 276 | * Removed redundant test_to_normal, now in zentest_mappings 277 | 278 | === 4.3.3 / 2010-06-17 279 | 280 | * 2 minor enhancements: 281 | 282 | * Added options and removed pattern from Autotest::RCov 283 | * update_rubygems now deletes cached rubygems installs 284 | 285 | === 4.3.2 / 2010-06-02 286 | 287 | * 1 minor enhancement: 288 | 289 | * Removed support for rbx builds. 290 | 291 | * 2 bug fixes: 292 | 293 | * Removed 'preview' from version filter so I can test with 1.9.2 294 | * Put PATH tweaks at front of PATH 295 | 296 | === 4.3.1 / 2010-03-30 297 | 298 | * 1 bug fix: 299 | 300 | * Fixed autotest output on 1.8. That's what I get for not having 301 | tests for dots. 302 | 303 | === 4.3.0 / 2010-03-27 304 | 305 | * 1 minor enhancement: 306 | 307 | * Added gemcutter rubyforge and hoe to the_usual setup 308 | 309 | * 4 bug fixes: 310 | 311 | * 1.9 compat: don't use putc anymore. (fistfvck (hah!)) 312 | * Added note that included articles are out of date 313 | * Fixed rcov plugin so multiple all_good hooks can coexist 314 | * Fixed typo in doco 315 | 316 | === 4.2.1 / 2009-12-09 317 | 318 | * 1 minor enhancement: 319 | 320 | * Added GEM_HOME/GEM_PATH setting to multiruby. 321 | 322 | * 1 bug fix: 323 | 324 | * Fixed multiruby refactoring bug from previous release. 325 | 326 | === 4.2.0 / 2009-12-08 327 | 328 | * 3 minor enhancements: 329 | 330 | * Added 'multiruby -1 $version' for easier execution (idea from flori) 331 | * Set up and tear down PATH in multiruby (luis) 332 | * died hook now gets passed the exception (amikula) 333 | 334 | * 3 bug fixes: 335 | 336 | * Deal with windoze getc returning nil (undees) 337 | * Fix unit_diff for multiline miniunit results. 338 | * Fix warning for 1.8.8+ 339 | 340 | === 4.1.4 / 2009-08-07 341 | 342 | * 2 minor enhancements: 343 | 344 | * Added ability to prepend file mappings in autotest. (irohiroki) 345 | * Switched autodiscover to use Gem.find_files. 346 | 347 | * 2 bug fixes: 348 | 349 | * Updated doco for API changes. (David Ruan) 350 | * Updated git URL for Rubinius. jbarnette 351 | 352 | === 4.1.3 / 2009-06-23 353 | 354 | * 1 bug fix: 355 | 356 | * Fixed rakefile to include seattlerb plugin. release was flubbed 357 | 358 | === 4.1.2 / 2009-06-23 359 | 360 | * 1 minor enhancement: 361 | 362 | * multiruby configure now passes --enable-shared to support wilson and friends. 363 | 364 | * 2 bug fixes: 365 | 366 | * Delete RUBYOPT in multiruby_setup to avoid catastrophe. dbalatero 367 | * Fixed version number munging for tarballs. 368 | 369 | === 4.1.1 / 2009-06-03 370 | 371 | * 1 bug fix: 372 | 373 | * some calls to Multiruby.run didn't have logging. fixed (jcoglan) 374 | 375 | === 4.1.0 / 2009-06-03 376 | 377 | * 1 major enhancement: 378 | 379 | * Removed all plugins I don't want to maintain anymore. (gem inst autotest-rails) 380 | 381 | * 4 minor enhancements: 382 | 383 | * Added #blur to focus.rb to nuke all other test classes 384 | * Clear RUBYOPT before installs (balatero) 385 | * Removed test/unit include in zentest so we can focus on miniunit 386 | * sort versions for builds. (Jens Wille) 387 | 388 | * 6 bug fixes: 389 | 390 | * Fixed a stupid bug when running on some versions of bash >= 3.2.x and using '&>' in a system call. (Jens Wille) 391 | * Fixed inconsistent test commands in autotest. (jbarnette) 392 | * Fixed unit_diff's usage generation when installed as gem. (borior) 393 | * Fixed zentest to deal with unnamed classes. (Bill Dortch) 394 | * Removed buggy/unused -l support in unit_diff. (borior) 395 | * make twice to make it more resilient to makefile parallel bugs 396 | 397 | === 4.0.0 / 2009-03-02 398 | 399 | * 2 minor enhancements: 400 | 401 | * Deleted autotest/screen - releasing as a separate gem soon. 402 | * Deleted test-rails and rails_test_audit. 403 | 404 | * 8 minor enhancements: 405 | 406 | * Added "tags" command to multiruby_setup. 407 | * Added "the_usual" as a recipe for multiruby_setup. Motivated by Dr. Nic. 408 | * Added :died hook to Autotest (yoshuki). 409 | * Added focus.rb, helping you ignore extra tests while you focus on something. 410 | * Added multigem command line tool! 411 | * Cleaned up multiruby to make it easier to tweak. 412 | * Parameterized testlib so you can override test/unit in autotest. Thanks JB! 413 | * Switched tests and rakefile to minitest. 20% faster! 414 | 415 | * 7 bug fixes: 416 | 417 | * Fix Autotest::Screen to distinguish between errors and failures. (khalsah) 418 | * Fixed some hook commands from overriding others. 419 | * Hopefully fixed growl support on 10.5.x? 420 | * Supposedly adding -w to growlnotify will fix autotest/growl. 421 | * Updated missing entries in Autotest::ALL_HOOKS. 422 | * Updated rubygems download location. (carletti) 423 | * Still not automated :( 424 | * ruby 1.9.1 fixes. 425 | 426 | === 3.11.1 / 2009-01-20 427 | 428 | * 1 minor enhancement: 429 | 430 | * Parameterized test_lib so you can override test/unit. Thanks JB! 431 | 432 | * 1 bug fix: 433 | 434 | * Fixed growl autotest plugin hooks to not return true, preempting other plugins. 435 | 436 | === 3.11.0 / 2008-10-22 437 | 438 | * 19 minor enhancements: 439 | 440 | * Added :updated hook, gets list of updated files before running tests. 441 | * Added autotest/restart.rb - restarts autotest if .autotest updated. 442 | * Added better help to multiruby. 443 | * Added dummy build command to multiruby_setup. 444 | * Added git support. 445 | * Added rbx:ln:$dir and rbx:git:current. 446 | * Added rubygems:merge as a nice little hack to share rubygems setups. 447 | * Added svn tag updating (eg will svn sw from mri 1.8.6 222 to 1.8.6 231). 448 | * Autotest hooks now take *args as well as instance of autotest. 449 | * Made it possible to have manually specified tags. 450 | * Made multiruby a little more self-repairing wrt symlinks and build dirs. 451 | * Refactored into mri_latest_tag(v). 452 | * Refactored unit_diff to make it a bit easier to use as a library. 453 | * Refactored zentest mapping main methods into munge/unmunge. 454 | * Removed rubinius specific symlink hacks, now fully supported. 455 | * mri:svn:releases figures out all the latest patch levels. 456 | * multiruby_setup clean now checks for rakefile first, since rbx has both. :( 457 | * multiruby_setup help now exits instead of building. 458 | * multiruby_setup list and clean now exit 459 | 460 | * 2 bug fixes: 461 | 462 | * ZenTestMapping converts operator prefixes to operators only when an _ follows. 463 | * Apparently Tempfile.open doesn't return it's last value. fixed. 464 | 465 | * 2 bug fixes: 466 | 467 | * Fixed bug in mri:svn:branch:xxx with svn dir name. 468 | * multiruby_setup rm now smarter about tarballs. 469 | 470 | === 3.10.0 / 2008-06-17 471 | 472 | * 1 major enhancement: 473 | 474 | * Added multiruby_setup to help manage multiruby installed versions. 475 | 476 | * 3 minor enhancements: 477 | 478 | * Added autotest/once plugin to help plugin developers. 479 | * Heavily refactored multiruby. 480 | * Switched rubinius from shotgun/rubinius to bin/rbx, finally. 481 | 482 | * 2 bug fixes: 483 | 484 | * Refactored zentest_mapping test to avoid zentest altogether. 485 | * zentest tests bail gracefully for rubinius. 486 | 487 | === 3.9.3 / 2008-06-09 488 | 489 | * 12 minor enhancements: 490 | 491 | * Added $RUBY env support to autotest so you can swap what ruby to run. 492 | * Added ALL_HOOKS array to autotest for hook devs. 493 | * Added EXCLUDED_VERSIONS to multiruby. Integrated with hoe. 494 | * Added miniunit compatibility to unit_diff's output. 495 | * Multiruby now determines the latest versions 1.8/1.9 automatically. 496 | * Removed deprecated :run hook. 497 | * Fixed zentest_assertions to be compatible with miniunit. Will phase out. 498 | * Minor autotest plugin cleanup / fixes. 499 | * Moved assert_callback to test/rails/test_case.rb 500 | * Reversed assert_includes' arguments. 501 | * Updated requirements info for other ruby impls. 502 | * util_capture now returns strings, not iostrings. 503 | 504 | * 1 bug fixes: 505 | 506 | * (add|remove)_(mappings|exceptions) now all return nil to help fix autotest hooks. 507 | 508 | === 3.9.2 / 2008-03-20 509 | 510 | * 4 minor enhancements: 511 | 512 | * Added compatibility with miniunit differences. 513 | * Added email_notify, jabber_notify, and rcov autotest plugins. 514 | * Updated rakefile to include examples automatically in example_dot_autotest. 515 | * multiruby now outputs each command so you can grab it easily. 516 | 517 | * 5 bug fixes: 518 | 519 | * Ensure tests are run after reset. 520 | * Fixed all test/rails tests to run in any combo. 521 | * Fixed up growl.rb a bit... still buggy (growlnotify, not growl.rb). 522 | * Fixes for -f (fast start) and last_mtime in general. 523 | * Fixes for 1.9 and rubinius 524 | 525 | === 3.9.1 / 2008-01-31 526 | 527 | * 1 bug fix: 528 | 529 | * OMG I'm so dumb... fixed memory leak. 530 | 531 | === 3.9.0 / 2008-01-30 532 | 533 | * 15 minor enhancements: 534 | 535 | * Added Wilson's patch to allow unit_diff to work with mspec. Adding rspec next. 536 | * Minor overhaul for autotest: 537 | * Added -f flag to start up without testing. 538 | * Added -q flag to autotest to make it extra quiet. Patch by Aaron Patterson. 539 | * Added ability to set test execution order, defaults to :random. EVIL! 540 | * Added completed_re and failed_results_re to help subclasses like rspec. 541 | * Added deprecation warnings for hooks. Deprecated :run. 542 | * Added find_directories accessor, defaults to ['.'] 543 | * Added sleep accessor, defaults to 1 second. 544 | * Changed find_files to order files in the same order as find_directories. 545 | * Changed how autodiscover works with $:, added lib to the front. 546 | * Cleaned out nearly every @ and use accessor methods instead. You should too. 547 | * Made test_mappings ordered. 548 | * Removed @files, adding @find_order and @known_files. 549 | * Renamed tests_for_file to test_files_for. 550 | * test_files_for now only returns known files. 551 | 552 | === 3.8.0 / 2008-01-12 553 | 554 | * 10 minor enhancements: 555 | 556 | * Added basic support for rubinius in multiruby. 557 | * Changed Dunno! message to only output on -v 558 | * Added Getting Started with Autotest by Philippe Hanrigou (with permission) 559 | * Updated example_dot_autotest.rb for newer plugins. 560 | * Cleaned up rdoc. 561 | * Worked with David Chelimsky to make Autotest more uber for subclasses. 562 | * Removed exceptions and test_mappings accessors and replaced with add/remove/clear methods. Updating .autotest should be very straightforward. 563 | * Moved :initialize hook to beginning of run method 564 | * Changed load/customization order to be: 565 | * Autotest 566 | * AutotestSubClass 567 | * ~/.autotest 568 | * ./.autotest (yes, both .autotest files). 569 | * Moved away from using instance variables to encourage subclasses to use accessors. 570 | 571 | === 3.7.2 / 2008-01-09 572 | 573 | * 2 minor enhancements: 574 | 575 | * Extended file map for tests to include subdirs correctly. 576 | * Added debugging output on bad maps if -v set. 577 | 578 | === 3.7.1 / 2007-12-27 579 | 580 | * 2 minor enhancements: 581 | 582 | * multiruby now downloads 1.8.6 and 1.9 on virgin run. 583 | * Improved output for the downloads. 584 | 585 | === 3.7.0 / 2007-12-21 586 | 587 | * 8 minor enhancements: 588 | 589 | * Added add_mapping to make file mappings cleaner. 590 | * Added assert_callback thanks to Aaron Patterson. 591 | * Added autotest/cctray. 592 | * Added extra_files and extra_class_map, allowing .autotest files to be awesome. 593 | * Added url for lettuce principal thanks to Hugh Sasse. 594 | * Added zentest.rb refactorings thanks to Hugh Sasse. 595 | * Exceptions are now an array of regexps, built after :initialize hook. 596 | * Removed ruby_fork and ruby_fork_client. Eric got a faster laptop. :P 597 | 598 | * 6 bug fixes: 599 | 600 | * Fixed all my annoyances with @exceptions. 601 | * Fixed crasher in autotest/redgreen for non-matches. 602 | * Fixed everything to work with ruby 1.9. 603 | * Fixed rubygem requires causing strangeness in tests. 604 | * Fixed zentest mapping so ruby2ruby and test_ruby2ruby work. 605 | * Removed stupid YAML methods from TrueClass during 606 | testing. (Infected by Test::Rails' use of rubygems) 607 | 608 | === 3.6.1 / 2007-07-23 609 | 610 | * 4 minor enhancements: 611 | 612 | * Test::Rails::ViewTestCase now uses assert_select. 613 | * assert_form and friends now work with blocks like assert_select 614 | does. 615 | * Allow path_parameters in view tests to be ammended, making working 616 | with routes easier. 617 | * New version of autotest/notify.rb uses notify-send. 618 | * Fixed rdoc formatting on autotest and a couple plugins. 619 | 620 | === 3.6.0 / 2007-05-25 621 | 622 | * 4 major enhancements: 623 | 624 | * New auto-discovery mechanism to make rspec and friends work independently! 625 | * Moved and restructured camping and rails as plugins. 626 | * Removed rspec - now packaged with rspec and/or as plugin. 627 | * Changed the way FTM tests are named. Allows multiple matricies. 628 | 629 | * 3 minor enhancements: 630 | 631 | * Added :OK special result value to FTM. 632 | * Hugh Sasse is awesome. Rdoc happiness. 633 | * Parameterized emacs client command. 634 | 635 | * 3 bug fixes: 636 | 637 | * Dup load path because I'm dum. 638 | * Fixed a lame syntax error in emacs.rb. 639 | * autotest now builds command separator with '&' on windoze. ARGH! Why is this the first I've heard of this?!? 640 | 641 | === 3.5.2 / 2007-04-30 642 | 643 | * 4 bug fixes: 644 | 645 | * Patch up Rails fixture defaults for Test::Rails::TestCase. 646 | * Session now properly hooked up to controllers. 647 | * ruby 1.8.6 has a bug on 'raise Interrupt' with no args. Fixed on both sides. 648 | * Fixed redgreen to work with new getc/putc-based output. (from Finn Smith) 649 | 650 | === 3.5.1 / 2007-04-17 651 | 652 | * 4 bug fixes: 653 | 654 | * Fixed gem name to be camel-case again. Fixed on rubyforge too. 655 | * Fixed rdoc for hooks. 656 | * Fixed redgreen, results changed to an array. 657 | * Patch up Rails fixture defaults, since they're not inheriting properly. 658 | 659 | === 3.5.0 / 2007-04-12 660 | 661 | * 4 major enhancements: 662 | 663 | * Now requires RubyGems 0.9.1 or newer. 664 | * Autotest and unit_diff are both unbuffered. Results are more live. 665 | * Refactored and redesigned how files map to test from Sean Carley. See fixtures plugin as an example. 666 | * Generalize how autotest handler is instantiated and invoked, allowing for many more autotest types, including combos. 667 | 668 | * 23 minor enhancements: 669 | 670 | * Added all_good hook if initial run was all_good as well. 671 | * Added assert_in_epsilon to ZentestAssertions. 672 | * Added autotest plugin to auto-update source ala tinderbox/cruisecontrol. 673 | * Added autotest plugin to update ichat/adium IM status with code stats. 674 | * Added autotest plugin to update the GNU screen statusbar from Yuichi Tateno. 675 | * Added autotest syntax error handling from Ryan Platte. 676 | * Added autotest/emacs emacs integration plugin!!! YAY! 677 | * Added autotest/migrate.rb. 678 | * Added camping support from Geoffrey Grossenbach. 679 | * Added changed file reporting to autotest via -v flag. 680 | * Added informative summary and filtering via ENV['VERSIONS'] to multiruby. 681 | * Added libnotify support from Kazuo Saito. 682 | * Added lots of rdoc patches from Hugh Sasse. 683 | * Added rjs files to view_test_case. 684 | * Added rspec_autotest "stolen" from caldersphere.net 685 | * Added run_command hook to trigger the start of a test run. 686 | * Added tmp to rails' exceptions list. 687 | * Added unit_diff command variable to autotest for customizing flags and such. 688 | * Added zentest_mapping.rb and test. 689 | * Allow session to work in Test::Rails view tests. 690 | * Improved autotest/growl.rb output from imajes. 691 | * Improved autotest/timestamp output from Josh Susser. 692 | * Test::Rails works with Rails 1.2. 693 | 694 | * 2 bug fixes: 695 | 696 | * Accelerated Test::Rails unit tests via sensible defaults. 697 | * Better assertion messages for ZentestAssertions. 698 | 699 | === 3.4.3 / 2006-12-19 700 | 701 | * 2 minor enhancements: 702 | 703 | * Add assert_title and assert_h (for header). 704 | 705 | * 2 bug fixes: 706 | 707 | * Rereleased against latest version of hoe to fix load path problems. 708 | * Fix case ViewTestCase for case-sensitive file systems. 709 | 710 | === 3.4.2 / 2006-11-09 711 | 712 | * 2 minor enhancements: 713 | 714 | * Add TextHelper for pluralize. 715 | * Add deny_nil to Test::Rails. 716 | 717 | * 7 bug fixes: 718 | 719 | * Fixed test_help's Flash. It's is a module. Oops... 720 | * Don't run util_audit_assert_assigned if tests didn't pass, results will be bogus. 721 | * Fixed AssertionsTest names to match what autotest expects. 722 | * Fixed bug where deny_includes failed for Symbol keys. 723 | * Switched autotest to use require instead of load... Why??? I don't know!! 724 | * Fixed a minor but annoying whitespace difference in unit_diff. 725 | * Switched argument order of assert_includes and deny_includes to match Test::Unit convention. 726 | 727 | === 3.4.1 / 2006-10-13 728 | 729 | * 3 minor enhancements: 730 | 731 | * FUNDAMENTALLY changed the way failures map back to tests. This REQUIRES users of autotest to ensure that their tests and impls map 1:1 at every scoping level. I'll blog more details. 732 | * Hoe'd rakefile 733 | * Added support for render :collection to RenderTree. 734 | 735 | * 7 bug fixes: 736 | 737 | * Fixed autotest tests for custom ruby names. 738 | * Fixed some documentation errors in ControllerTestCase. 739 | * Fixed setup in FunctionalTestCase. 740 | * Allowed @assigns_ignored to contain either Symbols or Strings, bug 5233. 741 | * Using Object.path2class to look up classes in helper test cases, bug 5493. 742 | * Added assert_text_area, bug 5452. 743 | * Renamed assert_select to assert_select_tag. Stupid rails. We were here first. 744 | 745 | === 3.4.0 / 2006-09-12 746 | 747 | * 13 minor enhancements: 748 | 749 | * Broke out example_dot_autotest into multiple files in lib. 750 | * Enhanced hook system so it can return true if event handled. 751 | * Sleep is now 1 second by default because life is too short. 752 | * Hooked interrupt with new hook system. First handler wins. 753 | * Hooked test results before output 754 | * Accurate test counts for Test::Rails. 755 | * Added snarl autotest plugin, thanks to Patrick Hurley. 756 | * Added timestamp autotest plugin, thanks to Joe Goldberg. 757 | * Added redgreen, thanks to Pat Eyler, Sean Carley, and Rob Sanheim. 758 | * Added kdenotify autotest plugin, thanks to Geir Freysson. 759 | * Added markaby support for Test::Rails. 760 | * Added hack to display a tree of render calls. 761 | * Added hook to perform extra setup for 762 | 763 | * 5 bug fixes: 764 | 765 | * Extended zentest to deal with rails a bit better... ugh. 766 | * Fixed @libs for windoze. 767 | * Fixed inner class/test identification in autotest. 768 | * Namespaced all plugins... eric is anal. 769 | * No longer freak out if rubygems tarball not in multiruby/versions. 770 | 771 | === 3.3.0 / 2006-07-28 772 | 773 | * 1 major enhancement: 774 | 775 | * autotest has been rewritten to be much cleaner, now has a plugin system. 776 | 777 | * 5 minor enhancements: 778 | 779 | * test/rails adds helper tests, "stolen" from Geoff's work, (which was "stolen" from ryan's work. :P) 780 | * autotest turnaround is now faster. 781 | * Added more prune dirs to autotest. 782 | * test/rails rewinds IOs containing captured output. Added assert_empty. 783 | * Document that autotest doesn't run the db:test:prepare rake task when in Rails mode. 784 | * Added ruby_fork, but haven't fully plugged into autotest yet. 785 | 786 | * 7 bug fixes: 787 | 788 | * Add SIGINT handler to unit_diff to give a more graceful exit. 789 | * Don't strip <> from tempfiles, parse_diff does it for us. 790 | * Fixed autotest problems on windoze. Ugh. 791 | * Fixed broken pipe bug and newline bug in unit_diff. 792 | * Make request_method in ControllerTestCase a String. 793 | * multitest installs rubygems if tarball found in versions dir. 794 | * multitest only configures when makefile is missing. Rebuilds much faster now. 795 | * ruby_fork exits without backtrace and allows redirection of output. 796 | 797 | === 3.2.0 / 2006-04-10 798 | 799 | * 1 major enhancement: 800 | 801 | * Added Test::Rails. 802 | 803 | * 1 minor enhancement: 804 | 805 | * Extended autotest for Test::Rails. 806 | 807 | * 4 bug fixes: 808 | 809 | * Autotest now detects changes in rhtml. 810 | * Improved autotest's file mapping and choice of ruby. 811 | * We've got RDoc, yes we do! 812 | * Removed redundancies in rakefile. Using gem spec for most stuff now. 813 | 814 | === 3.1.0 / 2006-03-29 815 | 816 | * 2 major enhancements: 817 | 818 | * Added multiruby! YAY! 819 | * Massive improvements to autotest: speed, reliability, reporting, etc. 820 | 821 | * 10 minor enhancements: 822 | 823 | * multiruby builds in a centralized location. YAY! 824 | * multiruby now allows reinstalls quickly and easily (can even skip config). 825 | * multiruby exits with total sum of exit codes. 826 | * autotest file search is muuuuch faster. 827 | * autotest automatically detects rails mode. 828 | * autotest deals with rails dependencies much better. 829 | * autotest reruns a full suite after you go green to ensure full coverage. 830 | * autotest always runs with unit_diff -u. 831 | * autotest can now run cvs/svn/p4 up periodically to be a mini-tinderbox. 832 | * autotest now has real help. 833 | 834 | * 4 bug fixes: 835 | 836 | * ZenTest is now zentest. Yay for consistency! (do a rake uninstall to clean) 837 | * ZenTest excludes pretty_print methods. 838 | * Fixed unary operator issues (they were backwards... oops!) for ZenTest. 839 | * unit_diff now runs diff.exe on Windoze. dunno if that will work. 840 | 841 | === 3.0.0 / 2006-03-06 842 | 843 | * 2 major enhancements: 844 | 845 | * Added autotest and rails_autotest. YAY for continous testing! 846 | * Repackaged and gemified. YAY for gemification! 847 | 848 | * 3 minor enhancements: 849 | 850 | * Added non-mappable tests starting with test_integration_. 851 | * Lots of code and test refactoring and cleanup. 852 | * Massive improvement on unit tests. 853 | 854 | * 3 bug fixes: 855 | 856 | * Cleaned up class method inheritence. Esp relevant for rails testing. 857 | * Finally fixed the unit_diff parse bug! 858 | * Fixed improper counting of errors if a class was missing, should be 1 + missing methods. 859 | 860 | === 2.4.0 / 2005-03-21 861 | 862 | * 3 minor enhancements: 863 | 864 | * Able to audit standard class library (so now we can audit rubicon!). 865 | * Able to map against class methods (self.blah <=> test_class_blah). 866 | * Added -I=rubypath support 867 | 868 | * 4 bug fixes: 869 | 870 | * bug:1151 Fixed stupid problem w/ unit_diff. 871 | * bug:1454 code generation correctly matches class/module for nested classes. 872 | * bug:1455 Updated method mapping to work on all operators listed in my quickref. 873 | * Realized I'm a moron and did NOT release in March like I thought... 874 | 875 | === 2.3.0 / 2004-11-18 876 | 877 | * 6 minor enhancements: 878 | 879 | * Massively expanded the method name mappings. 880 | * Added -r flag to reverse map names, for Rails style testing. 881 | * Added -e to auto eval tests generated. 882 | * Added -b & -c flags in unit_diff (passed to diff) 883 | * Added install and uninstall rules to Makefile. 884 | * Added some more doco to README.txt 885 | 886 | * 7 bug fixes: 887 | 888 | * Cleaned up and refactored tests. 889 | * Changed the way files are generated, to accomodate new flags. 890 | * Added some more tests. 891 | * Added $ZENTEST=true 892 | * Fixed the one-liner diff bug. 893 | * Cleaned up multi-line string diffs by unescaping \n 894 | * Cleaned result for flunks. 895 | 896 | === 2.2.0 / 2004-10-18 897 | 898 | * 4 minor enhancements: 899 | 900 | * Added LinuxJournalArticle.txt! WOOT! 901 | * Added unit_diff.rb - a very cool filter for test output! 902 | * Extended ZenTest to work with standard input 903 | * Added "ZenTest FULL" to force ZenTest to analyze inherited methods, for subclasses of the standard library. 904 | 905 | * 3 bug fixes: 906 | 907 | * Extended makefile to be more dynamic and stop diffing versions. ugh. 908 | * Expanded the method rename map to handle <<, *, +, and ==. 909 | * Added more test cases. 910 | 911 | === 2.1.2 / 2004-03-08 912 | 913 | * 4 bug fixes: 914 | 915 | * Fixed yet another 1.8ism, results will be the same in 1.8 and 1.6. 916 | * Fixed code responsible for method name conversion. 917 | * I am a moron... didn't run tests after updating version. 918 | * Need to convert my diff-based tests to unit tests. 919 | 920 | === 2.1.1 / 2004-03-06 921 | 922 | * 3 bug fixes: 923 | 924 | * Fixed a 1.8ism. 925 | * Removed zentestrunner. Older ruby users will just have to suffer. 926 | * Updated history to ZenWeb format. 927 | 928 | === 2.1.0 / 2003-01-07 929 | 930 | * 3 major enhancements: 931 | 932 | * Output is runnable as-is thanks to zentestrunner.rb. 933 | * Wrapped up all running functionality into ZenTest.fix 934 | * Added simple statistic output... Thanks Dave & Andy!!! 935 | 936 | * 2 minor enhancements: 937 | 938 | * Added zentestrunner.rb until Nathaniel accepts my changes! 939 | * Added a clean rule to Makefile 940 | 941 | * 3 bug fixes: 942 | 943 | * Removed at_exit override and avoided test/unit altogether. 944 | * Extended README.txt to include some of the rules. 945 | * Fixed several tests and added assertions for new stats 946 | 947 | === 2.0.0 / 2002-10-29 948 | 949 | * 2 major enhancements: 950 | 951 | * Rewrite of ZenTest.rb into actual OO design. 952 | * Added unit tests, finally... 953 | 954 | === 1.0.1 / 2002-09-28 955 | 956 | * 1 minor enhancement: 957 | 958 | * Only loads when a class is detected, allows some scripts to be skipped. 959 | 960 | * 3 bug fixes: 961 | 962 | * Cleaned up output. Verbose when $DEBUG is true. 963 | * Added an error count that is output at end. 964 | * Better filtering or conversion on some method names. 965 | 966 | === 1.0.0 / 2002-09-24 967 | 968 | * 1 major enhancement: 969 | 970 | * Birthday! 971 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | .autotest 2 | History.txt 3 | Manifest.txt 4 | README.txt 5 | Rakefile 6 | articles/Article.css 7 | articles/how_to_use_zentest.txt 8 | bin/multigem 9 | bin/multiruby 10 | bin/unit_diff 11 | bin/zentest 12 | example.txt 13 | example1.rb 14 | example2.rb 15 | lib/focus.rb 16 | lib/functional_test_matrix.rb 17 | lib/unit_diff.rb 18 | lib/zentest.rb 19 | lib/zentest_mapping.rb 20 | test/test_focus.rb 21 | test/test_unit_diff.rb 22 | test/test_zentest.rb 23 | test/test_zentest_mapping.rb 24 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | = ZenTest 2 | 3 | home :: https://github.com/seattlerb/zentest 4 | rdoc :: http://docs.seattlerb.org/ZenTest 5 | 6 | == DESCRIPTION 7 | 8 | ZenTest provides 4 different tools: zentest, unit_diff, autotest, and 9 | multiruby. 10 | 11 | zentest scans your target and unit-test code and writes your missing 12 | code based on simple naming rules, enabling XP at a much quicker pace. 13 | zentest only works with Ruby and Minitest or Test::Unit. There is 14 | enough evidence to show that this is still proving useful to users, so 15 | it stays. 16 | 17 | unit_diff is a command-line filter to diff expected results from 18 | actual results and allow you to quickly see exactly what is wrong. 19 | Do note that minitest 2.2+ provides an enhanced assert_equal obviating 20 | the need for unit_diff 21 | 22 | autotest is a continous testing facility meant to be used during 23 | development. As soon as you save a file, autotest will run the 24 | corresponding dependent tests. 25 | 26 | multiruby runs anything you want on multiple versions of ruby. Great 27 | for compatibility checking! Use multiruby_setup to manage your 28 | installed versions. 29 | 30 | *NOTE:* The next major release of zentest will not include autotest 31 | (use minitest-autotest instead) and multiruby will use rbenv / 32 | ruby-build for version management. 33 | 34 | == FEATURES 35 | 36 | * Scans your ruby code and tests and generates missing methods for you. 37 | * Includes a very helpful filter for Test/Spec output called unit_diff. 38 | * Continually and intelligently test only those files you change with autotest. 39 | * Test against multiple versions with multiruby. 40 | * Includes a LinuxJournal article on testing with ZenTest written by Pat Eyler. 41 | * See also: http://blog.zenspider.com/archives/zentest/ 42 | * See also: http://blog.segment7.net/articles/category/zentest 43 | 44 | == STRATEGERY 45 | 46 | There are two strategeries intended for ZenTest: test conformance 47 | auditing and rapid XP. 48 | 49 | For auditing, ZenTest provides an excellent means of finding methods 50 | that have slipped through the testing process. I've run it against my 51 | own software and found I missed a lot in a well tested 52 | package. Writing those tests found 4 bugs I had no idea existed. 53 | 54 | ZenTest can also be used to evaluate generated code and execute your 55 | tests, allowing for very rapid development of both tests and 56 | implementation. 57 | 58 | == AUTOTEST TIPS 59 | 60 | Setting up your project with a custom setup is easily done by creating 61 | a ".autotest" file in your project. Here is an example of adding some 62 | plugins, using minitest as your test library, and running rcov on full 63 | passes: 64 | 65 | require 'autotest/restart' 66 | 67 | Autotest.add_hook :initialize do |at| 68 | at.testlib = "minitest/autorun" 69 | end 70 | 71 | Autotest.add_hook :all_good do |at| 72 | system "rake rcov_info" 73 | end if ENV['RCOV'] 74 | 75 | Do note, since minitest ships with ruby19, if you want to use the 76 | latest minitest gem you need to ensure that the gem activation occurs! 77 | To do this, add the gem activation and the proper require to a 78 | separate file (like ".minitest.rb" or even a test helper if you have 79 | one) and use that for your testlib instead: 80 | 81 | .minitest.rb: 82 | 83 | gem "minitest" 84 | require "minitest/autorun" 85 | 86 | .autotest: 87 | 88 | Autotest.add_hook :initialize do |at| 89 | at.testlib = ".minitest" 90 | end 91 | 92 | If you prefer to suffix test files with "_test.rb" (instead of the 93 | default which prefixes test files with "test_") you can change the 94 | mapping by installing the autotest-suffix plugin. To do this first 95 | install the autotest-suffix gem: 96 | 97 | $ gem install autotest-suffix 98 | 99 | Then add the following to the ".autotest" file: 100 | 101 | require "autotest/suffix" 102 | 103 | If you prefer minitest/spec to minitest/unit, you can still use autotest 104 | by installing the autotest-spec plugin. 105 | To do this first install the autotest-spec gem: 106 | 107 | $ gem install autotest-spec 108 | 109 | Then add the following to the ".autotest" file: 110 | 111 | require "autotest/spec" 112 | 113 | == SYNOPSIS 114 | 115 | ZenTest MyProject.rb TestMyProject.rb > missing.rb 116 | 117 | ./TestMyProject.rb | unit_diff 118 | 119 | autotest 120 | 121 | multiruby_setup mri:svn:current 122 | multiruby ./TestMyProject.rb 123 | 124 | == Windows and Color 125 | 126 | Read this: http://blog.mmediasys.com/2010/11/24/we-all-love-colors/ 127 | 128 | == REQUIREMENTS 129 | 130 | * Ruby 1.8+, JRuby 1.1.2+, or rubinius 131 | * A test/spec framework of your choice. 132 | * Hoe (development) 133 | * rubygems, 1.8+ 134 | * diff.exe on windows. Use http://gnuwin32.sourceforge.net/packages.html 135 | 136 | == INSTALL 137 | 138 | * sudo gem install ZenTest 139 | 140 | == LICENSE 141 | 142 | (The MIT License) 143 | 144 | Copyright (c) Ryan Davis, Eric Hodel, seattle.rb 145 | 146 | Permission is hereby granted, free of charge, to any person obtaining 147 | a copy of this software and associated documentation files (the 148 | "Software"), to deal in the Software without restriction, including 149 | without limitation the rights to use, copy, modify, merge, publish, 150 | distribute, sublicense, and/or sell copies of the Software, and to 151 | permit persons to whom the Software is furnished to do so, subject to 152 | the following conditions: 153 | 154 | The above copyright notice and this permission notice shall be 155 | included in all copies or substantial portions of the Software. 156 | 157 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 158 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 159 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 160 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 161 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 162 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 163 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 164 | 165 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | $LOAD_PATH << 'lib' 4 | 5 | require 'rubygems' 6 | require 'hoe' 7 | 8 | Hoe.add_include_dirs("../../minitest/dev/lib") 9 | 10 | Hoe.plugin :seattlerb 11 | 12 | Hoe.spec "ZenTest" do 13 | developer 'Ryan Davis', 'ryand-ruby@zenspider.com' 14 | developer 'Eric Hodel', 'drbrain@segment7.net' 15 | 16 | license "MIT" 17 | end 18 | 19 | desc "run autotest on itself" 20 | task :autotest do 21 | ruby "-Ilib -w ./bin/autotest" 22 | end 23 | 24 | desc "update example_dot_autotest.rb with all possible constants" 25 | task :update do 26 | system "p4 edit example_dot_autotest.rb" 27 | File.open "example_dot_autotest.rb", "w" do |f| 28 | f.puts "# -*- ruby -*-" 29 | f.puts 30 | Dir.chdir "lib" do 31 | Dir["autotest/*.rb"].sort.each do |s| 32 | next if s =~ /rails|discover/ 33 | f.puts "# require '#{s[0..-4]}'" 34 | end 35 | end 36 | 37 | f.puts 38 | 39 | Dir["lib/autotest/*.rb"].sort.each do |file| 40 | file = File.read(file) 41 | m = file[/module.*/].split(/ /).last rescue nil 42 | next unless m 43 | 44 | file.grep(/def[^(]+=/).each do |setter| 45 | setter = setter.sub(/^ *def self\./, '').sub(/\s*=\s*/, ' = ') 46 | f.puts "# #{m}.#{setter}" 47 | end 48 | end 49 | end 50 | system "p4 diff -du example_dot_autotest.rb" 51 | end 52 | 53 | # vim:syntax=ruby 54 | 55 | -------------------------------------------------------------------------------- /articles/Article.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic styles for core document typography. 3 | * 4 | * Influenced by: 5 | * - "How to size text using ems" [http://www.clagnut.com/blog/348] 6 | */ 7 | 8 | /*************************** Basic Typography ******************************/ 9 | 10 | a:link { color: black; } /* unvisited link */ 11 | a:visited { color: black; } /* visited link */ 12 | a:hover { color: black; } /* mouse over link */ 13 | a:active { color: black; } /* selected link */ 14 | 15 | html { 16 | background: white; 17 | color: black; 18 | text-align: center; /* IE hack to workaround for block centering. */ 19 | margin: 0; 20 | padding: 0; 21 | } 22 | 23 | 24 | body { 25 | margin: 0; 26 | padding: 0; 27 | font-family: Lucida Grande, sans-serif; 28 | } 29 | 30 | * { 31 | font-family: Lucida Grande, sans-serif; 32 | } 33 | 34 | img { vertical-align: middle; } 35 | 36 | p, li, dt, dd { 37 | line-height: 1.5; 38 | font-size: .95em; 39 | } 40 | 41 | p, ul, ol, dl { 42 | margin-left: 3em; 43 | margin-right: 2em; 44 | } 45 | 46 | p + p { 47 | text-indent: 1.4em; 48 | } 49 | 50 | dl { 51 | margin: 1em 3em; 52 | } 53 | 54 | dl dt a { 55 | font: 1.5em bold Lucida Grande, sans-serif; 56 | } 57 | 58 | dl dt { 59 | font-weight: bold; 60 | } 61 | 62 | dl dd { 63 | margin: 0em 3em; 64 | } 65 | 66 | ul { 67 | list-style: square outside; 68 | } 69 | 70 | li p { /* Markdown define paragraphs for list items. */ 71 | margin: 0; 72 | } 73 | 74 | /* 75 | * Ensure that nested items have the same size as their parent as we are 76 | * using (relative) em sizing. 77 | */ 78 | li li, li p, td p, blockquote p { 79 | font-size: 1em; 80 | } 81 | 82 | abbr, acronym { 83 | letter-spacing:0.1em 84 | font-variant: small-caps; 85 | } 86 | 87 | em { 88 | font-style: italic; 89 | } 90 | 91 | term { 92 | font-style: italic; 93 | } 94 | 95 | cite { 96 | font-style: italic; 97 | } 98 | input, select, th, td {font-size:1em} 99 | 100 | /******************************* Headers ********************************/ 101 | 102 | h1 { 103 | text-align: center; 104 | padding: .3em 2em 0em 2em; 105 | margin: 0; 106 | color: black; 107 | font-size: 1.3em; 108 | } 109 | 110 | h2 { 111 | margin-left: 0.4em; 112 | font-weight: bold; 113 | font-size: 1.2em; 114 | border-bottom: medium #5089da solid; 115 | } 116 | 117 | h3 { 118 | font-size: 1.2em; 119 | margin-left: .5em; 120 | margin-right: auto; 121 | color: #929292; 122 | border-bottom: thin solid #929292; 123 | } 124 | 125 | /************************ Editing / Authoring ********************/ 126 | 127 | .todo { 128 | background: red; 129 | color: yellow; 130 | font-weight: bold; 131 | } 132 | 133 | /************************ Samples, Input, Code, Commands ********************/ 134 | 135 | code { 136 | font-family: monospace; 137 | } 138 | 139 | 140 | 141 | kbd:before { 142 | content: open-quote; 143 | } 144 | 145 | kbd:after { 146 | content: close-quote; 147 | } 148 | 149 | 150 | code, kbd, var, pre { 151 | font-family: monaco, "Courier New", courier, monospace; 152 | font-size: 14px; 153 | } 154 | 155 | .command-box { 156 | clear: right; /* Side notes. */ 157 | border: 1px dotted #888; 158 | background: #151515; 159 | color: #eee; 160 | margin: .5em 2em .5em 3em; 161 | padding: .5em; 162 | text-align: left; 163 | font-family: monospace; 164 | -moz-border-radius: .5em; 165 | -webkit-border-radius: .5em; 166 | border-radius: .5em; 167 | 168 | } 169 | 170 | span.placeholder { 171 | font-style: italic; 172 | } 173 | 174 | span.placeholder:before { 175 | content: "<"; 176 | } 177 | 178 | span.placeholder:after { 179 | content: ">"; 180 | } 181 | 182 | .command-box span.placeholder { 183 | font-style: normal; 184 | color: #cc2; 185 | } 186 | 187 | .source-code-box { 188 | border: 1px dotted #888; 189 | background: #151515; 190 | color: #eee; 191 | margin: .5em 2em .5em 3em; 192 | padding: .5em; 193 | text-align: left; 194 | font-family: monospace; 195 | -moz-border-radius: .5em; 196 | -webkit-border-radius: .5em; 197 | border-radius: .5em; 198 | } 199 | 200 | .output-box { 201 | border: 1px dotted #888; 202 | background: #151515; 203 | color: #eee; 204 | margin: .5em 2em .5em 3em; 205 | padding: .5em; 206 | text-align: left; 207 | font-family: monospace; 208 | } 209 | 210 | .sample-box { 211 | border: 1px dotted #444; 212 | margin: .5em 2em .5em 3em; 213 | padding: .5em; 214 | text-align: left; 215 | } 216 | 217 | /* 218 | * Global styles for PH Web 219 | */ 220 | 221 | /* 222 | * Banner and Main navigation menu 223 | */ 224 | 225 | div.banner { 226 | margin: 0; 227 | padding: 0; 228 | float: left; 229 | width: 100%; 230 | font-size: 110%; 231 | line-height: normal; 232 | background: #5089da url( Main-Menu-Background-2.jpg ) repeat-y bottom left; 233 | border-bottom: thin #666 solid; 234 | } 235 | 236 | div.banner div.signature span.by { 237 | font: italic .7em Didot, serif; 238 | padding-right: .6em; 239 | text-shadow: .2em .2em .2em #222; 240 | } 241 | 242 | div.banner div.signature { 243 | float: right; 244 | color: gainsboro; 245 | font: 1.1em Didot, serif; 246 | margin: 0; 247 | padding: 60px .1em 0 0; 248 | vertical-align: baseline; 249 | } 250 | 251 | img#banner-logo { 252 | float: left; 253 | margin: .3em 6em .1em 1em; 254 | border: 0; 255 | } 256 | 257 | 258 | div.banner a#feed_link img { 259 | vertical-align: middle; 260 | border: 0; 261 | float: right; 262 | margin: .5em 1em; 263 | } 264 | 265 | div.banner a#contact_me_link img { 266 | vertical-align: middle; 267 | border: 0; 268 | float: right; 269 | margin: .5em 1em; 270 | } 271 | 272 | ul.section-menu { 273 | float: left; 274 | margin: 0 auto; 275 | padding: 0; 276 | width: 40em; 277 | list-style: none; 278 | } 279 | 280 | ul.section-menu li { 281 | display:block; 282 | float: left; 283 | margin: .8em 1px .5em 1px; 284 | padding: 1em 0; 285 | background: transparent url( 'Button Gradient.png' ) repeat-x center; 286 | text-shadow: .1em .1em .2em #444; 287 | } 288 | 289 | 290 | ul.section-menu li a:visited { 291 | color: gainsboro; 292 | } 293 | 294 | ul.section-menu li a { 295 | padding: 0 1em; 296 | margin: 0; 297 | font: bold .9em Lucida Grande, sans-serif; 298 | text-decoration: none; 299 | color: gainsboro; 300 | } 301 | 302 | ul.section-menu li.first a { 303 | padding-left: .6em; 304 | } 305 | 306 | ul.section-menu li.last a { 307 | padding-left: .6em; 308 | } 309 | 310 | ul.section-menu li a:hover { 311 | color: white; 312 | } 313 | 314 | ul.section-menu li#current a { 315 | color: #ff8; 316 | } 317 | 318 | div.content { 319 | float: left; 320 | background: url(Shore.jpg) no-repeat left top; 321 | width: 100%; 322 | padding: 0; 323 | padding-top: 1.3em; 324 | padding-bottom: 1.3em; 325 | text-align: left; 326 | clear: both; 327 | margin: 0; 328 | } 329 | 330 | 331 | p.Cartouche { 332 | float: right; 333 | width: 15em; 334 | margin: 0 2em 2em 3em; 335 | background: #5089da; 336 | padding: 1em; 337 | text-align: center; 338 | color: white; 339 | font-weight: bold; 340 | border: 0; 341 | text-indent: 0; 342 | 343 | -moz-border-radius: .5em; 344 | -webkit-border-radius: .5em; 345 | border-radius: .5em; 346 | -webkit-box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.5) 347 | } 348 | 349 | p.Cartouche a img { 350 | border: 0; 351 | } 352 | 353 | .Copyright { 354 | clear: both; 355 | text-align: center; 356 | font: italic 1em Lucida Grande, sans-serif; 357 | color: #444; 358 | margin-bottom: .2em; 359 | } 360 | 361 | .License { 362 | margin-top: 0; 363 | font: italic 1em Lucida Grande, sans-serif; 364 | color: #444; 365 | } 366 | 367 | a.discussion_link { 368 | text-decoration: none; 369 | } 370 | 371 | a.comment_counter { 372 | color: #5089da; 373 | padding: 0 1.5em; 374 | } 375 | 376 | h2.document_reference a { 377 | text-decoration: none; 378 | } 379 | 380 | span.tag_list { 381 | padding-left: 4em; 382 | font-weight: normal; 383 | } 384 | 385 | span.tag_list img { 386 | vertical-align: middle; 387 | } 388 | 389 | span.tag_list:before { 390 | content: '['; 391 | } 392 | 393 | span.tag_list:after { 394 | content: ']'; 395 | } 396 | 397 | p.read_more, p.read_more a { 398 | font-variant : small-caps; 399 | font-weight: bold; 400 | color: #5089da; 401 | } 402 | 403 | h2 span.post_date { 404 | padding-right: 1em; 405 | color: #5089da; 406 | } 407 | 408 | div#contact_form { 409 | margin: 2em; 410 | } 411 | 412 | div#contact_form label { 413 | margin-bottom: 100px; 414 | padding: 5em 1em 5em 0; 415 | } 416 | 417 | div#contact_form textarea { 418 | margin-top: 1.5em; 419 | width: 50em; 420 | } 421 | 422 | div.errorExplanation { 423 | background: #fbb; 424 | color: red; 425 | padding: 1em; 426 | margin: 1em; 427 | -moz-border-radius: 1em; 428 | -webkit-border-radius: 1em; 429 | border-radius: 1em; 430 | } 431 | 432 | div.errorExplanation h2 { 433 | font-size: 1em; 434 | border: 0; 435 | color: red; 436 | } 437 | 438 | /* 439 | * Round box 440 | */ 441 | table.box { 442 | table-layout: fixed; 443 | border-spacing: 0; 444 | border: none; 445 | } 446 | 447 | table.box tr, table.box tr td { 448 | padding: 0; 449 | margin: 0; 450 | border: none; 451 | } 452 | 453 | table.box tr.top, table.box tr.bottom { 454 | height: 30px; 455 | } 456 | 457 | table.box tr.bottom td { /* Fix for Safari who does not pick up height from the rule. */ 458 | height: 30px; 459 | } 460 | 461 | table.box tr td.left, table.box tr td.right { 462 | width: 30px; 463 | } 464 | 465 | table.box tr.top td.left { 466 | background: url('Blue Box Top Left.png') no-repeat top left; 467 | } 468 | 469 | table.box tr.top td.center { 470 | background: url('Blue Box Top.png') repeat-x top; 471 | } 472 | 473 | table.box tr.top td.right { 474 | background: url('Blue Box Top Right.png') no-repeat top right; 475 | } 476 | 477 | table.box tr.middle td.left { 478 | background: url('Blue Box Left.png') repeat-y left; 479 | } 480 | 481 | table.box tr.middle td.center { 482 | background: url('Blue Box Center.png') repeat; 483 | padding: 0px; 484 | } 485 | 486 | table.box tr.middle td.right { 487 | background: url('Blue Box Right.png') repeat-y right; 488 | } 489 | 490 | table.box tr.bottom td.left { 491 | background: url('Blue Box Bottom Left.png') no-repeat bottom left; 492 | } 493 | 494 | table.box tr.bottom td.center { 495 | background: url('Blue Box Bottom.png') repeat-x bottom; 496 | } 497 | 498 | table.box tr.bottom td.right { 499 | background: url('Blue Box Bottom Right.png') no-repeat bottom right; 500 | } 501 | 502 | 503 | /* 504 | * Styles for PH articles. 505 | */ 506 | 507 | div.content { 508 | float: left; 509 | width: 90%; 510 | padding: 0 3em; 511 | } 512 | 513 | div.content * { 514 | text-align: left; 515 | } 516 | 517 | div.content h1 { 518 | padding: 1em 2em 1em 2em; 519 | text-align: center; 520 | font-size: 1.7em; 521 | text-shadow: .1em .1em .2em #666; 522 | } 523 | 524 | div.content table.reference-table { 525 | margin-left: 4em; 526 | } 527 | div.content table.reference-table tr td, div.content table.reference-table tr th { 528 | padding: .3em 1em; 529 | text-align: left; 530 | } 531 | 532 | div.Header { 533 | margin: .5em 3em 1em 3em; 534 | padding: .5em 1em .5em 1em; 535 | background: #999; 536 | color: #fff; 537 | border: thin solid #888; 538 | text-align: center; 539 | } 540 | 541 | div.Author { 542 | padding: 0; 543 | margin: 0; 544 | color: #fff; 545 | font: italic 1em serif; 546 | } 547 | 548 | div.Author:before { 549 | content: "By " 550 | } 551 | 552 | div.LastUpdate:before { 553 | content: "Last significant update: " 554 | } 555 | 556 | p.LastUpdate { 557 | padding: 0; 558 | margin: -.5em 0 1.2em 0; 559 | color: #222; 560 | font: italic 1em sans-serif; 561 | text-align: center; 562 | } 563 | 564 | div.Abstract { 565 | margin: .5em 6em; 566 | background: #5089da; 567 | color: white; 568 | text-align: justify; 569 | padding: 1.2em 1.2em; 570 | 571 | -moz-border-radius: 1em; 572 | -webkit-border-radius: 1em; 573 | border-radius: 1em; 574 | -webkit-box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.5) 575 | } 576 | 577 | div.Abstract p { 578 | margin: 1em 0 0 0; 579 | padding: 0; 580 | } 581 | 582 | div.Abstract p.first { 583 | margin-top: 0; 584 | } 585 | 586 | div.Audience:before { 587 | content: "Audience: "; 588 | } 589 | 590 | div.Audience { 591 | margin: .5em 2em; 592 | color: #444; 593 | font: italic 1em Verdana sans-serif; 594 | text-align: justify; 595 | } 596 | 597 | 598 | h2 { 599 | margin-top: 1.5em; 600 | margin-left: 0em; 601 | font-weight: bold; 602 | font-size: 1.2em; 603 | border-bottom: medium #5089da solid; 604 | text-shadow: .1em .1em .2em #666; 605 | } 606 | 607 | h3 { 608 | font-size: 1.1em; 609 | margin-top: 1em; 610 | margin-left: 1em; 611 | margin-right: auto; 612 | color: #5089da; 613 | border-bottom: thin solid #929292; 614 | text-shadow: .1em .1em .2em #bbb; 615 | } 616 | 617 | h4 { 618 | font-size: 1em; 619 | font-weight: bold; 620 | margin-left: 2em; 621 | margin-right: auto; 622 | color: #000; 623 | border-bottom: thin #5089da solid; 624 | } 625 | 626 | div#what-s-next { 627 | margin: .5em 0em; 628 | background: #5089da; 629 | color: white; 630 | text-align: justify; 631 | padding: 1.5em 1em 1em 1em; 632 | 633 | -moz-border-radius: 1em; 634 | -webkit-border-radius: 1em; 635 | border-radius: 1em; 636 | -webkit-box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.5) 637 | } 638 | 639 | div#what-s-next h2 { 640 | color: white; 641 | padding-top: 0em; 642 | padding: 0; 643 | margin: 0; 644 | } 645 | 646 | div#what-s-next h3 { 647 | color: #060c14; 648 | border-color: #305282; 649 | } 650 | 651 | div.side-note { 652 | float: right; 653 | width: 23em; 654 | margin: 0 0 1.2em 2em; 655 | padding: 0; 656 | 657 | background: lightyellow; 658 | border: thin solid #a8a86d; 659 | } 660 | 661 | div.side-note .header { 662 | margin: .25em 1em; 663 | padding: 0 0 .3em 0; 664 | 665 | color: black; 666 | border-bottom: solid medium #5089da; 667 | } 668 | 669 | div.side-note p { 670 | margin: .5em 1em; 671 | } 672 | 673 | div.side-note .header p { 674 | text-align: center; 675 | font-weight: bold; 676 | margin: 0; 677 | padding: 0; 678 | } 679 | 680 | h2, h3 { 681 | clear: both; 682 | } 683 | 684 | div.maruku_toc ul { 685 | margin: 0 2em; 686 | padding: 0; 687 | } 688 | 689 | div.maruku_toc ul li ul { 690 | margin: 0 2em; 691 | padding: 0; 692 | } 693 | 694 | div.content li { 695 | margin-top: 1em; 696 | margin-bottom: 1em; 697 | } 698 | 699 | blockquote { 700 | color: #333; 701 | font-style: Italic; 702 | } 703 | 704 | .ruby .normal {} 705 | .ruby .comment { color: #005; font-style: italic; } 706 | .ruby .keyword { color: #A00; font-weight: bold; } 707 | .ruby .method { color: #077; } 708 | .ruby .class { color: #074; } 709 | .ruby .module { color: #050; } 710 | .ruby .punct { color: #447; font-weight: bold; } 711 | .ruby .symbol { color: #099; } 712 | .ruby .string { color: #944; background: #FFE; } 713 | .ruby .char { color: #F07; } 714 | .ruby .ident { color: #004; } 715 | .ruby .constant { color: #07F; } 716 | .ruby .regex { color: #B66; background: #FEF; } 717 | .ruby .number { color: #F99; } 718 | .ruby .attribute { color: #7BB; } 719 | .ruby .global { color: #7FB; } 720 | .ruby .expr { color: #227; } 721 | .ruby .escape { color: #277; } 722 | -------------------------------------------------------------------------------- /articles/how_to_use_zentest.txt: -------------------------------------------------------------------------------- 1 | How to Use ZenTest with Ruby 2 | by Pat Eyler 3 | http://linuxjournal.com/article.php?sid=7776 4 | (included in this package with permission) 5 | 6 | Refactoring and unit testing are a great pair of tools for every 7 | programmer's workbench. Sadly, not every programmer knows how to use 8 | them. My first exposure to them came when I started using Ruby, 9 | refactoring and unit testing are a big part of the landscape in the 10 | Ruby community. 11 | 12 | Some time ago, I translated the refactoring example from the first 13 | chapter of Martin Fowler's excellent book, Refactoring, out of Java 14 | and into Ruby. I felt this would be a great way to learn more about 15 | refactoring and brush up on my Ruby while I was at it. Recently, I 16 | decided to update the translation for Ruby 1.8.X. One of the things I 17 | needed to change was to convert the old unit tests to work with 18 | Test::Unit, the new unit testing framework for Ruby. 19 | 20 | I wasn't really looking forward to building a new test suite though. 21 | Fortunately, help was available. Ryan Davis has written a great tool 22 | called ZenTest, which creates test suites for existing bodies of 23 | code. Since a lot of people are new to refactoring, unit testing, and 24 | ZenTest, I thought this would be a great chance to introduce you to 25 | this trio of tools. 26 | 27 | Martin's example code is built around a video store application. In 28 | his original code, there are three classes; Customer, Movie, and 29 | Rental. I'll focus on just the Customer class in this article. 30 | Here's the original code: 31 | 32 | class Customer 33 | attr_accessor :name 34 | 35 | def initialize(name) 36 | @name = name 37 | @rentals = Array.new 38 | end 39 | 40 | def addRental(aRental) 41 | @rentals.push(aRental) 42 | end 43 | 44 | def statement 45 | totalAmount = 0.0 46 | frequentRenterPoints = 0 47 | rentals = @rentals.length 48 | result = "\nRental Record for #{@name}\n" 49 | thisAmount = 0.0 50 | @rentals.each do |rental| 51 | # determine amounts for each line 52 | case rental.aMovie.pricecode 53 | when Movie::REGULAR 54 | thisAmount += 2 55 | if rental.daysRented > 2 56 | thisAmount += (rental.daysRented - 2) * 1.5 57 | end 58 | 59 | when Movie::NEW_RELEASE 60 | thisAmount += rental.daysRented * 3 61 | 62 | when Movie::CHILDRENS 63 | thisAmount += 1.5 64 | if each.daysRented > 3 65 | thisAmount += (rental.daysRented - 3) * 1.5 66 | end 67 | 68 | end 69 | 70 | # add frequent renter points 71 | frequentRenterPoints += 1 72 | # add bonus for a two day new release rental 73 | if ( rental.daysRented > 1) && 74 | (Movie::NEW_RELEASE == rental.aMovie.pricecode) 75 | frequentRenterPoints += 1 76 | end 77 | 78 | # show figures for this rental 79 | result +="\t#{rental.aMovie.title}\t#{thisAmount}\n" 80 | totalAmount += thisAmount 81 | end 82 | result += "Amount owed is #{totalAmount}\n" 83 | result += "You earned #{frequentRenterPoints} frequent renter points" 84 | end 85 | end 86 | 87 | 88 | Not the cleanest code in the world, but it is supposed to be that 89 | way. This represents the code as you get it from the user. No 90 | tests, poorly laid out, but working -- and it's your job to make it 91 | better without breaking it. So, where to start? With unit tests of 92 | course. 93 | 94 | Time to grab ZenTest. You can run it like this: 95 | 96 | $ zentest videostore.rb > test_videostore.rb 97 | 98 | which produces a file full of tests. Running the test suite doesn't 99 | do quite what we were hoping though: 100 | 101 | $ ruby testVideoStore.rb Loaded suite testVideoStore 102 | Started 103 | EEEEEEEEEEE 104 | Finished in 0.008974 seconds. 105 | 106 | 1) Error!!! 107 | test_addRental(TestCustomer): 108 | NotImplementedError: Need to write test_addRental 109 | testVideoStore.rb:11:in `test_addRental' 110 | testVideoStore.rb:54 111 | 112 | 2) Error!!! 113 | test_name=(TestCustomer): 114 | NotImplementedError: Need to write test_name= 115 | testVideoStore.rb:15:in `test_name=' 116 | testVideoStore.rb:54 117 | 118 | 3) Error!!! 119 | test_statement=(TestCustomer): 120 | NotImplementedError: Need to write test_statement 121 | testVideoStore.rb:19:in `test_statement' 122 | testVideoStore.rb:54 123 | . 124 | . 125 | . 126 | 127 | 11 tests, 0 assertions, 0 failures, 11 errors 128 | $ 129 | 130 | So what exactly did we get out of this? Here's the portion of our 131 | new test suite that matters for the Customer class: 132 | 133 | # Code Generated by ZenTest v. 2.1.2 134 | # classname: asrt / meth = ratio% 135 | # Customer: 0 / 3 = 0.00% 136 | 137 | require 'test/unit' 138 | 139 | class TestCustomer < Test::Unit::TestCase 140 | def test_addRental 141 | raise NotImplementedError, 'Need to write test_addRental' 142 | end 143 | 144 | def test_name= 145 | raise NotImplementedError, 'Need to write test_name=' 146 | end 147 | 148 | def test_statement 149 | raise NotImplementedError, 'Need to write test_statement' 150 | end 151 | end 152 | 153 | ZenTest built three test methods: one for the accessor method, one for 154 | the addRental method, and one for the statement method. Why nothing 155 | for the initializer? Well, initializers tend to be pretty bulletproof 156 | (if they're not, it's pretty easy to add the test method yourself). 157 | Besides, we'll be testing it indirectly when we write test_name= (the 158 | tests for the accessor method). There's one other thing we'll need to 159 | add, the test suite doesn't load the code we're testing. Changing the 160 | beginning of the script to require the videostore.rb file will do the 161 | trick for us. 162 | 163 | 164 | # Code Generated by ZenTest v. 2.1.2 165 | # classname: asrt / meth = ratio% 166 | # Customer: 0 / 3 = 0.00% 167 | 168 | require 'test/unit' 169 | require 'videostore' 170 | 171 | That little snippet of comments at the top lets us know that we have three 172 | methods under test in the Customer class, zero assertions testing 173 | them, and no coverage. Let's fix that. We'll start by writing some 174 | tests for test_name= (no, it really doesn't matter what order we go in -- 175 | this is just a convenient place to start). 176 | 177 | def test_name= 178 | aCustomer = Customer.new("Fred Jones") 179 | assert_equal("Fred Jones",aCustomer.name) 180 | aCustomer.name = "Freddy Jones" 181 | assert_equal("Freddy Jones",aCustomer.name 182 | end 183 | 184 | Running testVideoStore.rb again gives us: 185 | 186 | $ ruby testVideoStore.rb 187 | Loaded suite testVideoStore 188 | Started 189 | E.EEEEEEEEE 190 | Finished in 0.011233 seconds. 191 | 192 | 1) Error!!! 193 | test_addRental(TestCustomer): 194 | NotImplementedError: Need to write test_addRental 195 | testVideoStore.rb:13:in `test_addRental' 196 | testVideoStore.rb:58 197 | 198 | 2) Error!!! 199 | test_statement(TestCustomer): 200 | NotImplementedError: Need to write test_statement 201 | testVideoStore.rb:23:in `test_statement' 202 | testVideoStore.rb:58 203 | . 204 | . 205 | . 206 | 11 tests, 2 assertions, 0 failures, 10 errors 207 | $ 208 | 209 | So far, so good. The line of 'E's (which shows errors in the test run) 210 | has been reduced by one, and the summary line at the bottom tells us 211 | roughly the same thing. 212 | 213 | We really don't have a way to test addRental directly, so we'll just 214 | write an stub test for now. 215 | 216 | def test_addRental 217 | assert(1) # stub test, since there is nothing in the method to test 218 | end 219 | 220 | When we run the tests again, we get: 221 | 222 | $ ruby testVideoStore.rb 223 | Loaded suite testVideoStore 224 | Started 225 | ..EEEEEEEEE 226 | Finished in 0.008682 seconds. 227 | 228 | 1) Error!!! 229 | test_statement(TestCustomer): 230 | NotImplementedError: Need to write test_statement 231 | testVideoStore.rb:22:in `test_statement' 232 | testVideoStore.rb:57 233 | . 234 | . 235 | . 236 | 11 tests, 3 assertions, 0 failures, 9 errors 237 | $ 238 | 239 | Better and better, just one error left in the TestCustomer class. 240 | Let's finish up with a test that will clear our test_statement error 241 | and verify that addRental works correctly: 242 | 243 | def test_statement 244 | aMovie = Movie.new("Legacy",0) 245 | 246 | aRental = Rental.new(aMovie,2) 247 | 248 | aCustomer = Customer.new("Fred Jones") 249 | aCustomer.addRental(aRental) 250 | aStatement = "\nRental Record for Fred Jones\n\tLegacy\t2.0 251 | Amount owed is 2.0\nYou earned 1 frequent renter points" 252 | 253 | assert_equal(aStatement,aCustomer.statement) 254 | 255 | end 256 | 257 | We run the tests again, and see: 258 | 259 | $ ruby testVideoStore.rb 260 | Loaded suite testVideoStore 261 | Started 262 | ...EEEEEEEE 263 | Finished in 0.009378 seconds. 264 | . 265 | . 266 | . 267 | 11 tests, 4 assertions, 0 failures, 8 errors 268 | $ 269 | 270 | Great! The only errors left are on the Movie and Rental classes, 271 | the Customer class is clean. 272 | 273 | We can continue along like this for the remaining classes, but I'll 274 | not bore you with those details. Instead, I'd like to look at how 275 | ZenTest can help when you've already got some tests in place. Later 276 | development allows us to do just that -- the video store owner 277 | wants a new web based statement for web using customers. 278 | 279 | After a bit of refactoring and new development, the code looks like 280 | this: 281 | 282 | class Customer 283 | attr_accessor :name 284 | 285 | def initialize(name) 286 | @name = name 287 | @rentals = Array.new 288 | end 289 | 290 | def addRental(aRental) 291 | @rentals.push(aRental) 292 | end 293 | 294 | def statement 295 | result = "\nRental Record for #{@name}\n" 296 | @rentals.each do 297 | |each| 298 | # show figures for this rental 299 | result +="\t#{each.aMovie.title}\t#{each.getCharge}\n" 300 | end 301 | result += "Amount owed is #{getTotalCharge}\n" 302 | result += 303 | "You earned #{getFrequentRenterPoints} frequent renter points" 304 | end 305 | 306 | def htmlStatement 307 | result = "\n

Rentals for #{name}

\n" 308 | @rentals.each do 309 | |each| 310 | result += "#{each.aMovie.title}: #{each.getCharge}
\n" 311 | end 312 | result += "You owe #{getTotalCharge}

\n" 313 | result += 314 | "On this rental you earned #{getFrequentRenterPoints}" + 315 | " frequent renter points

" 316 | end 317 | 318 | def getTotalCharge 319 | result = 0.0 320 | @rentals.each do 321 | |each| 322 | result += each.getCharge() 323 | end 324 | result 325 | end 326 | 327 | def getFrequentRenterPoints 328 | result = 0 329 | @rentals.each do 330 | |each| 331 | result += each.getFrequentRenterPoints 332 | end 333 | result 334 | end 335 | end 336 | 337 | There's a lot of new stuff in here. If we run ZenTest again, it'll 338 | pick up the methods we don't have any coverage on (we should have 339 | written them as we wrote the new methods, but this is a bit more 340 | illustrative). This time, we'll invoke ZenTest a little bit 341 | differently: 342 | 343 | $ zentest videostore.rb testVideoStore.rb > Missing_tests 344 | 345 | and our (trimmed) output looks like this: 346 | 347 | # Code Generated by ZenTest v. 2.1.2 348 | # classname: asrt / meth = ratio% 349 | # Customer: 4 / 6 = 66.67% 350 | 351 | 352 | require 'test/unit' 353 | 354 | class TestCustomer < Test::Unit::TestCase 355 | def test_getFrequentRenterPoints 356 | raise NotImplementedError, 357 | 'Need to write test_getFrequentRenterPoints' 358 | end 359 | 360 | def test_getTotalCharge 361 | raise NotImplementedError, 'Need to write test_getTotalCharge' 362 | end 363 | 364 | def test_htmlStatement 365 | raise NotImplementedError, 'Need to write test_htmlStatement' 366 | end 367 | end 368 | 369 | Hmmm, three more test methods to fill in to get our complete 370 | coverage. As we write these, we can just migrate them into our 371 | existing testVideoStore.rb test suite. Then we can keep moving ahead 372 | with refactoring and adding new features. In the future, let's just 373 | be sure we add tests as we go along. ZenTest can help you here too. 374 | You can write stubs for new development, then run ZenTest to create 375 | your new test stubs as well. After some refactorings (like 'extract 376 | method'), ZenTest can be used the same way. 377 | 378 | Refactoring and unit testing are powerful tools for programmers, and 379 | ZenTest provides an easy way to start using them in a Ruby 380 | environment. Hopefully, this introduction has whetted your appetite. 381 | 382 | If you're interested in learning more about refactoring, please grab a 383 | copy of 'Refactoring: Improving the Design of Existing Code' and take 384 | a look at www.refactoring.com. For more information about unit 385 | testing, please see: c2.com/cgi/wiki?UnitTest, 386 | www.junit.org/index.htm, and 387 | www.extremeprogramming.org/rules/unittests.html. 388 | 389 | The latest information about Test::Unit and ZenTest are available at 390 | their home pages: testunit.talbott.ws (for Test::Unit) and 391 | www.zenspider.com/ZSS/Products/ZenTest. 392 | 393 | 394 | -------------------------------------------------------------------------------- /bin/multigem: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -w 2 | 3 | multiruby = File.expand_path "../multiruby", __FILE__ 4 | 5 | exec multiruby, "-S", "gem", *ARGV 6 | -------------------------------------------------------------------------------- /bin/multiruby: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -w 2 | 3 | require "yaml" 4 | 5 | class Array 6 | def human_sort 7 | sort_by { |item| item.to_s.split(/(\d+)/).map { |e| [e.to_i, e] } } 8 | end 9 | end 10 | 11 | root_dir = File.expand_path "~/.rubies" 12 | 13 | versions = Dir.chdir(root_dir) { Dir["*"] }.human_sort 14 | 15 | def setenv dir 16 | ENV["PATH"] = "#{dir}/bin:#{ENV["PATH"]}" 17 | end 18 | 19 | def unsetenv key 20 | if ENV[key] then 21 | warn "WARNING: %s is set to %p. Removing..." % [key, ENV[key]] 22 | ENV.delete key 23 | end 24 | end 25 | 26 | unsetenv "GEM_HOME" 27 | unsetenv "GEM_PATH" 28 | 29 | ## 30 | # multiruby -1 2.0 ruby_args... 31 | 32 | if ARGV.first == "-1" then 33 | ARGV.shift 34 | vers = Dir["#{root_dir}/#{ARGV.shift}*"] 35 | 36 | abort "ambiguous version: #{vers.map { |p| File.basename p }.inspect}" if 37 | vers.size != 1 38 | 39 | dir = vers.first 40 | setenv dir 41 | 42 | exec "#{dir}/bin/ruby", *ARGV 43 | end 44 | 45 | def maybe_load_yaml_file config 46 | if config then 47 | if YAML.respond_to? :safe_load_file then 48 | YAML.safe_load_file config, permitted_classes: [Regexp, Symbol] 49 | else 50 | YAML.load_file config 51 | end 52 | end 53 | end 54 | 55 | rcpath = File.expand_path "~/.hoerc" 56 | skip = if File.exist? rcpath then 57 | conf = maybe_load_yaml_file rcpath 58 | conf["multiruby_skip"] || [] 59 | end 60 | excl = (ENV["EXCLUDED_VERSIONS"] || "").split(/:/) + skip 61 | unless excl.empty? then 62 | excludes = Regexp.union(*excl) 63 | versions = versions.delete_if { |v| v =~ excludes } 64 | end 65 | 66 | # safekeep original PATH 67 | original_path = ENV['PATH'] 68 | 69 | results = {} 70 | versions.each do |version| 71 | dir = "#{root_dir}/#{version}" 72 | ruby = "#{dir}/bin/ruby" 73 | ver = version.delete_prefix "ruby-" 74 | 75 | puts 76 | puts "VERSION = #{ver}" 77 | cmd = [ruby, ARGV].flatten.map { |s| s =~ /\"/ ? "'#{s}'" : s }.join(' ') 78 | cmd.sub!(/#{ENV['HOME']}/, '~') 79 | puts "CMD = #{cmd}" 80 | puts 81 | 82 | setenv dir 83 | 84 | system ruby, *ARGV 85 | puts 86 | puts "RESULT = #{$?}" 87 | results[ver] = $? 88 | 89 | # restore the path to original state 90 | ENV['PATH'] = original_path 91 | end 92 | 93 | passed, failed = results.keys.partition { |v| results[v] == 0 } 94 | 95 | puts 96 | puts "TOTAL RESULT = #{failed.size} failures out of #{results.size}" 97 | puts 98 | puts "Passed: #{passed.join(", ")}" 99 | puts "Failed: #{failed.join(", ")}" 100 | 101 | exit failed.size 102 | -------------------------------------------------------------------------------- /bin/unit_diff: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -ws 2 | # 3 | # unit_diff - a ruby unit test filter by Ryan Davis 4 | # 5 | # usage: 6 | # 7 | # test.rb | unit_diff [options] 8 | # options: 9 | # -b ignore whitespace differences 10 | # -c contextual diff 11 | # -h show usage 12 | # -k keep temp diff files around 13 | # -u unified diff [default] 14 | # -p plain diff 15 | # -v display version 16 | 17 | require 'unit_diff' 18 | require 'zentest' 19 | 20 | ############################################################ 21 | 22 | if defined? $v then 23 | puts "#{File.basename $0} v. #{ZenTest::VERSION}" 24 | exit 0 25 | end 26 | 27 | if defined? $h then 28 | File.open(__FILE__) do |f| 29 | begin; end until f.readline =~ /usage:/ 30 | f.readline 31 | while line = f.readline and line.sub!(/^# ?/, '') 32 | $stderr.puts line 33 | end 34 | end 35 | exit 0 36 | end 37 | 38 | UnitDiff.unit_diff 39 | -------------------------------------------------------------------------------- /bin/zentest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby -swI . 2 | 3 | require 'zentest' 4 | 5 | $TESTING = true # for ZenWeb and any other testing infrastructure code 6 | 7 | if defined? $v then 8 | puts "#{File.basename $0} v#{ZenTest::VERSION}" 9 | exit 0 10 | end 11 | 12 | if defined? $h then 13 | ZenTest.usage_with_exit 14 | end 15 | 16 | code = ZenTest.fix(*ARGV) 17 | if defined? $e then 18 | require 'test/unit' 19 | eval code 20 | else 21 | print code 22 | end 23 | 24 | -------------------------------------------------------------------------------- /example.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | What do we do to get people writing tests? 5 | What do we do to get people writing tests first? 6 | 7 | I didn't know it's name, but apparently it's the Lettuce Principal, 8 | (see http://blog.toolshed.com/2003/03/the_lettuce_pri.html). 9 | 10 | We NEED to make testing as easy as possible to get them testing. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Class Under Test Test Class 27 | ###################################################################### 28 | 29 | module Something module TestSomething 30 | class Thingy class TestThingy 31 | def do_something def test_do_something_normal 32 | # ... thingy = Thingy.new 33 | end result = thingy.do_something 34 | end assert(result.blahblah) 35 | end end 36 | def test_do_something_edgecase 37 | thingy = Thingy.new 38 | result = thingy.do_something 39 | assert(result.blahblah) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /example1.rb: -------------------------------------------------------------------------------- 1 | module Something 2 | class Thingy 3 | def do_something 4 | # ... 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /example2.rb: -------------------------------------------------------------------------------- 1 | module TestSomething 2 | class TestThingy 3 | def test_do_something_normal 4 | thingy = Thingy.new 5 | result = thingy.do_something 6 | assert(result.blahblah) 7 | end 8 | def test_do_something_edgecase 9 | thingy = Thingy.new 10 | result = thingy.do_something 11 | assert(result.blahblah) 12 | end 13 | end 14 | end 15 | 16 | -------------------------------------------------------------------------------- /lib/focus.rb: -------------------------------------------------------------------------------- 1 | class Module 2 | def focus *wanteds 3 | wanteds.map! { |m| m.to_s } 4 | unwanteds = public_instance_methods(false).grep(/test_/).map(&:to_s) 5 | unwanteds -= wanteds 6 | unwanteds.each do |unwanted| 7 | remove_method unwanted 8 | end 9 | end 10 | 11 | def focus_re regexp 12 | focus(*public_instance_methods.grep(regexp)) 13 | end 14 | 15 | def blur 16 | parent = self.superclass 17 | 18 | ObjectSpace.each_object Class do |klass| 19 | next unless parent > klass 20 | next if klass == self 21 | 22 | klass.send :focus 23 | klass.send :undef_method, :default_test 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/functional_test_matrix.rb: -------------------------------------------------------------------------------- 1 | 2 | ######################################################################## 3 | # The Idea: 4 | # 5 | # This is supposed to get us thinking about the various dimensions our 6 | # testing should address. If there are states orthogonal to each other 7 | # (eg. readable vs unreadable, logged in vs not logged in) each of 8 | # those states should comprise a dimension in the matrix. By 9 | # addressing it this way, we should be able to minimize the amount of 10 | # setup/teardown code and get full coverage across our actions for all 11 | # these edge cases and as a result have extremely clear tests. 12 | # 13 | ######################################################################## 14 | # Example Test Matrix Specification: 15 | # 16 | # matrix :example, :edge1, :edge2, :edge3, ... 17 | # action :action1, :OK, :e_NF, :mod, ... 18 | # action :action2, :OK, :e_RO, :na, ... 19 | # action ... 20 | # 21 | ######################################################################## 22 | # Matrix: 23 | # 24 | # I envision the setups being a code that combines the different 25 | # dimensions of edge case state. 26 | # 27 | # Something for a CMS might look like: `[df]_[ugo]_[rRwW]` where: 28 | # 29 | # + `[df]` for dir/file. 30 | # + and the rest is in the style of symbolic args to chmod: 31 | # + u/g/o = user, group, or other 32 | # + lowercase `X` == `X`able, uppercase `X` == un`X`able, where `X` 33 | # is read/write. 34 | # 35 | ######################################################################## 36 | # Action: 37 | # 38 | # :new/:err/:del are just examples, they should have semantic info 39 | # attached to them. 40 | # 41 | # Use :na to specify an inapplicable edge case for that action. 42 | # 43 | # Use :OK to specify the standard positive state. It is equivalent to 44 | # a result with the same name as the action. (eg 45 | # matrix_test_index). This cleans up the matrix a lot and allows for 46 | # narrower and more readable columns. 47 | # 48 | # Edge cases specific to an action that fall outside the matrix are 49 | # regular tests. 50 | # 51 | ######################################################################## 52 | # Matrix Methods (the legos): 53 | # 54 | # Everything else basically equates to lego pieces: 55 | # 56 | # + There is one "init" method per matrix: matrix_init_#{descr}(setup_args) 57 | # + There is one "setup" method per action: matrix_setup_#{action}(setup, expect) 58 | # + There is one "test" method per result: matrix_test_#{result}(setup) 59 | # 60 | # Thus, for the matrix "example" above, the top left-most test will 61 | # end up calling: 62 | # 63 | # matrix_init_example(:edge1) 64 | # matrix_setup_action1(:edge1, :new) 65 | # matrix_test_new(:edge1) 66 | # 67 | # Read the action method for exact details. 68 | ######################################################################## 69 | 70 | module FunctionalTestMatrix 71 | def matrix(name, *setups) 72 | @@matrix, @@setups = name, setups 73 | end 74 | 75 | def action(action, *results) 76 | testcases = @@setups.zip(results).reject { |a,b| b == :na } 77 | testcases = Hash[*testcases.flatten] 78 | matrix = @@matrix # bind to local scope for define_method closure 79 | 80 | testcases.each do |setup, expected| 81 | expected_action = expected == :OK ? action : expected 82 | define_method "test_#{matrix}_#{action}_#{setup}" do 83 | @action = action 84 | send "matrix_init_#{matrix}", *setup.to_s.split(/_/).map {|c| c.intern } 85 | send "matrix_setup_#{action}", setup, expected 86 | send "matrix_test_#{expected_action}", setup 87 | end 88 | end 89 | end 90 | 91 | module_function :matrix, :action 92 | end 93 | -------------------------------------------------------------------------------- /lib/unit_diff.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'rbconfig' 3 | 4 | ## 5 | # UnitDiff makes reading Test::Unit output easy and fun. Instead of a 6 | # confusing jumble of text with nearly unnoticable changes like this: 7 | # 8 | # 1) Failure: 9 | # test_to_gpoints(RouteTest) [test/unit/route_test.rb:29]: 10 | # <"new GPolyline([\n new GPoint( 47.00000, -122.00000),\n new GPoint( 46.5000 11 | # 0, -122.50000),\n new GPoint( 46.75000, -122.75000),\n new GPoint( 46.00000, 12 | # -123.00000)])"> expected but was 13 | # <"new Gpolyline([\n new GPoint( 47.00000, -122.00000),\n new GPoint( 46.5000 14 | # 0, -122.50000),\n new GPoint( 46.75000, -122.75000),\n new GPoint( 46.00000, 15 | # -123.00000)])">. 16 | # 17 | # 18 | # You get an easy-to-read diff output like this: 19 | # 20 | # 1) Failure: 21 | # test_to_gpoints(RouteTest) [test/unit/route_test.rb:29]: 22 | # 1c1 23 | # < new GPolyline([ 24 | # --- 25 | # > new Gpolyline([ 26 | # 27 | # == Usage 28 | # 29 | # test.rb | unit_diff [options] 30 | # options: 31 | # -b ignore whitespace differences 32 | # -c contextual diff 33 | # -h show usage 34 | # -k keep temp diff files around 35 | # -l prefix line numbers on the diffs 36 | # -u unified diff [default] 37 | # -p plain diff 38 | # -v display version 39 | 40 | class UnitDiff 41 | 42 | WINDOZE = RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ 43 | 44 | DIFF = if WINDOZE 45 | 'diff.exe' 46 | else 47 | if system("gdiff", __FILE__, __FILE__) 48 | 'gdiff' # solaris and kin suck 49 | else 50 | 'diff' 51 | end 52 | end unless defined? DIFF 53 | 54 | ## 55 | # Handy wrapper for UnitDiff#unit_diff. 56 | 57 | def self.unit_diff 58 | trap 'INT' do exit 1 end 59 | puts UnitDiff.new.unit_diff 60 | end 61 | 62 | def parse_input(input, output) 63 | current = [] 64 | data = [] 65 | data << current 66 | print_lines = true 67 | 68 | term = "\nFinished".split(//).map { |c| c[0] } 69 | term_length = term.size 70 | 71 | old_sync = output.sync 72 | output.sync = true 73 | while line = input.gets 74 | case line 75 | when /^(Loaded suite|Started|# Running tests:)/ then 76 | print_lines = true 77 | output.puts line 78 | chars = [] 79 | while c = input.getc do 80 | output.putc c 81 | chars << c 82 | tail = chars[-term_length..-1] 83 | break if chars.size >= term_length and tail == term 84 | end 85 | output.puts input.gets # the rest of "Finished in..." 86 | output.puts 87 | next 88 | when /^\s*$/, /^\(?\s*\d+\) (Failure|Error):/, /^\d+\)/ then 89 | print_lines = false 90 | current = [] 91 | data << current 92 | when /^Finished in \d/ then 93 | print_lines = false 94 | end 95 | output.puts line if print_lines 96 | current << line 97 | end 98 | output.sync = old_sync 99 | data = data.reject { |o| o == ["\n"] or o.empty? } 100 | footer = data.pop 101 | 102 | data.map do |result| 103 | break if result.any? { |l| l =~ / expected( but was|, not)/ } 104 | 105 | header = result.find do |l| 106 | l =~ /^\(?\s*\d+\) (Failure|Error):/ 107 | end 108 | 109 | break unless header 110 | 111 | message_index = result.index(header) + 2 112 | 113 | result[message_index..-1] = result[message_index..-1].join 114 | end 115 | 116 | return data, footer 117 | end 118 | 119 | # Parses a single diff recording the header and what 120 | # was expected, and what was actually obtained. 121 | def parse_diff(result) 122 | header = [] 123 | expect = [] 124 | butwas = [] 125 | footer = [] 126 | state = :header 127 | 128 | until result.empty? do 129 | case state 130 | when :header then 131 | header << result.shift 132 | state = :expect if result.first =~ /^<|^Expected/ 133 | when :expect then 134 | case result.first 135 | when /^Expected (.*?) to equal (.*?):$/ then 136 | expect << $1 137 | butwas << $2 138 | state = :footer 139 | result.shift 140 | when /^Expected (.*?), not (.*)$/m then 141 | expect << $1 142 | butwas << $2 143 | state = :footer 144 | result.shift 145 | when /^Expected (.*?)$/ then 146 | expect << "#{$1}\n".dup 147 | result.shift 148 | when /^to equal / then 149 | state = :spec_butwas 150 | bw = result.shift.sub(/^to equal (.*):?$/, '\1') 151 | butwas << bw 152 | else 153 | ex = result.shift.dup 154 | state = :butwas if ex.sub!(/ expected( but was|, not)/, '') 155 | expect << ex 156 | end 157 | when :butwas then 158 | butwas = result[0..-1].map(&:dup) 159 | result.clear 160 | when :spec_butwas then 161 | if result.first =~ /^\s+\S+ at |^:\s*$/ 162 | state = :footer 163 | else 164 | butwas << result.shift.dup 165 | end 166 | when :footer then 167 | butwas.last.sub!(/:$/, '') 168 | footer = result.map {|l| l.chomp } 169 | result.clear 170 | else 171 | raise "unknown state #{state}" 172 | end 173 | end 174 | 175 | return header, expect, nil, footer if butwas.empty? 176 | 177 | expect.last.chomp! 178 | expect.first.sub!(/^<\"/, '') 179 | expect.last.sub!(/\">$/, '') 180 | 181 | butwas.last.chomp! 182 | butwas.last.chop! if butwas.last =~ /\.$/ 183 | butwas.first.sub!( /^<\"/, '') 184 | butwas.last.sub!(/\">$/, '') 185 | 186 | return header, expect, butwas, footer 187 | end 188 | 189 | ## 190 | # Scans Test::Unit output +input+ looking for comparison failures and makes 191 | # them easily readable by passing them through diff. 192 | 193 | def unit_diff(input=ARGF, output=$stdout) 194 | $b = false unless defined? $b 195 | $c = false unless defined? $c 196 | $k = false unless defined? $k 197 | $u = true unless defined? $u 198 | $p = false unless defined? $p 199 | 200 | data, footer = self.parse_input(input, output) 201 | 202 | output = [] 203 | 204 | # Output 205 | data.each do |result| 206 | if result.first =~ /Error/ then 207 | output.push result.join('') 208 | next 209 | end 210 | 211 | prefix, expect, butwas, result_footer = parse_diff(result) 212 | 213 | output.push prefix.compact.map {|line| line.strip}.join("\n") 214 | 215 | if butwas then 216 | output.push self.diff(expect, butwas) 217 | 218 | output.push result_footer 219 | output.push '' 220 | else 221 | output.push expect.join('') 222 | end 223 | end 224 | 225 | if footer then 226 | footer.shift if footer.first.strip.empty? 227 | output.push footer.compact.map {|line| line.strip}.join("\n") 228 | end 229 | 230 | return output.flatten.join("\n") 231 | end 232 | 233 | def diff expect, butwas 234 | output = nil 235 | 236 | Tempfile.open("expect") do |a| 237 | a.write(massage(expect)) 238 | a.rewind 239 | Tempfile.open("butwas") do |b| 240 | b.write(massage(butwas)) 241 | b.rewind 242 | 243 | diff_flags = $p ? "" : $c ? "-c" : "-u" 244 | diff_flags += " -b" if $b 245 | 246 | result = `#{DIFF} #{diff_flags} #{a.path} #{b.path}` 247 | result.sub!(/^\-\-\- .+/, "--- expected") 248 | result.sub!(/^\+\+\+ .+/, "+++ actual") 249 | 250 | output = if result.empty? then 251 | "[no difference--suspect ==]" 252 | else 253 | result.split(/\n/) 254 | end 255 | 256 | if $k then 257 | warn "moving #{a.path} to #{a.path}.keep" 258 | File.rename a.path, a.path + ".keep" 259 | warn "moving #{b.path} to #{b.path}.keep" 260 | File.rename b.path, b.path + ".keep" 261 | end 262 | end 263 | end 264 | 265 | output 266 | end 267 | 268 | def massage(data) 269 | # unescape newlines, strip <> from entire string 270 | data = data.join 271 | data = data.gsub(/\\n/, "\n").gsub(/0x[a-f0-9]+/m, '0xXXXXXX') + "\n" 272 | data += "\n" unless data[-1] == ?\n 273 | data 274 | end 275 | end 276 | -------------------------------------------------------------------------------- /lib/zentest.rb: -------------------------------------------------------------------------------- 1 | if (defined? RUBY_ENGINE) && RUBY_ENGINE == 'jruby' 2 | require 'java' 3 | JRuby.objectspace=true 4 | end 5 | 6 | $stdlib = {} 7 | if ObjectSpace.respond_to?(:loaded_classes, true) then 8 | ObjectSpace.loaded_classes(true).each do |m| 9 | $stdlib[m.name] = true if m.respond_to? :name 10 | end 11 | else 12 | ObjectSpace.each_object(Module) do |m| 13 | $stdlib[m.name] = true if m.respond_to? :name 14 | end 15 | end 16 | 17 | require 'zentest_mapping' 18 | 19 | $:.unshift( *$I.split(/:/) ) if defined? $I and String === $I 20 | $r = false unless defined? $r # reverse mapping for testclass names 21 | $t ||= false # test/unit instead of minitest 22 | 23 | if $r then 24 | # all this is needed because rails is retarded 25 | $-w = false 26 | $: << 'test' 27 | $: << 'lib' 28 | require 'config/environment' 29 | f = './app/controllers/application.rb' 30 | require f if test ?f, f 31 | end 32 | 33 | $TESTING = true 34 | 35 | class Module 36 | def zentest 37 | at_exit { ZenTest.autotest(self) } 38 | end 39 | end 40 | 41 | ## 42 | # ZenTest scans your target and unit-test code and writes your missing 43 | # code based on simple naming rules, enabling XP at a much quicker 44 | # pace. ZenTest only works with Ruby and Minitest or Test::Unit. 45 | # 46 | # == RULES 47 | # 48 | # ZenTest uses the following rules to figure out what code should be 49 | # generated: 50 | # 51 | # * Definition: 52 | # * CUT = Class Under Test 53 | # * TC = Test Class (for CUT) 54 | # * TC's name is the same as CUT w/ "Test" prepended at every scope level. 55 | # * Example: TestA::TestB vs A::B. 56 | # * CUT method names are used in CT, with "test_" prependend and optional "_ext" extensions for differentiating test case edge boundaries. 57 | # * Example: 58 | # * A::B#blah 59 | # * TestA::TestB#test_blah_normal 60 | # * TestA::TestB#test_blah_missing_file 61 | # * All naming conventions are bidirectional with the exception of test extensions. 62 | # 63 | # See ZenTestMapping for documentation on method naming. 64 | 65 | class ZenTest 66 | 67 | VERSION = "4.12.2" 68 | 69 | include ZenTestMapping 70 | 71 | if $TESTING then 72 | attr_reader :missing_methods 73 | attr_accessor :test_klasses 74 | attr_accessor :klasses 75 | attr_accessor :inherited_methods 76 | else 77 | def missing_methods; raise "Something is wack"; end 78 | end 79 | 80 | def initialize 81 | @result = [] 82 | @test_klasses = {} 83 | @klasses = {} 84 | @error_count = 0 85 | @inherited_methods = Hash.new { |h,k| h[k] = {} } 86 | # key = klassname, val = hash of methods => true 87 | @missing_methods = Hash.new { |h,k| h[k] = {} } 88 | end 89 | 90 | # load_file wraps require, skipping the loading of $0. 91 | def load_file(file) 92 | puts "# loading #{file} // #{$0}" if $DEBUG 93 | 94 | unless file == $0 then 95 | begin 96 | require file 97 | rescue LoadError => err 98 | puts "Could not load #{file}: #{err}" 99 | end 100 | else 101 | puts "# Skipping loading myself (#{file})" if $DEBUG 102 | end 103 | end 104 | 105 | # obtain the class klassname 106 | def get_class(klassname) 107 | klass = nil 108 | begin 109 | klass = klassname.split(/::/).inject(Object) { |k,n| k.const_get n } 110 | puts "# found class #{klass.name}" if $DEBUG 111 | rescue NameError 112 | end 113 | 114 | if klass.nil? and not $TESTING then 115 | puts "Could not figure out how to get #{klassname}..." 116 | puts "Report to support-zentest@zenspider.com w/ relevant source" 117 | end 118 | 119 | return klass 120 | end 121 | 122 | # Get the public instance, class and singleton methods for 123 | # class klass. If full is true, include the methods from 124 | # Kernel and other modules that get included. The methods 125 | # suite, new, pretty_print, pretty_print_cycle will not 126 | # be included in the resuting array. 127 | def get_methods_for(klass, full=false) 128 | klass = self.get_class(klass) if klass.kind_of? String 129 | 130 | # WTF? public_instance_methods: default vs true vs false = 3 answers 131 | # to_s on all results if ruby >= 1.9 132 | public_methods = klass.public_instance_methods(false) 133 | public_methods -= Kernel.methods unless full 134 | public_methods.map! { |m| m.to_s } 135 | public_methods -= %w(pretty_print pretty_print_cycle) 136 | 137 | klass_methods = klass.singleton_methods(full) 138 | klass_methods -= Class.public_methods(true) 139 | klass_methods = klass_methods.map { |m| "self.#{m}" } 140 | klass_methods -= %w(self.suite new) 141 | 142 | result = {} 143 | (public_methods + klass_methods).each do |meth| 144 | puts "# found method #{meth}" if $DEBUG 145 | result[meth] = true 146 | end 147 | 148 | return result 149 | end 150 | 151 | # Return the methods for class klass, as a hash with the 152 | # method nemas as keys, and true as the value for all keys. 153 | # Unless full is true, leave out the methods for Object which 154 | # all classes get. 155 | def get_inherited_methods_for(klass, full) 156 | klass = self.get_class(klass) if klass.kind_of? String 157 | 158 | klassmethods = {} 159 | if (klass.class.method_defined?(:superclass)) then 160 | superklass = klass.superclass 161 | if superklass then 162 | the_methods = superklass.instance_methods(true) 163 | 164 | # generally we don't test Object's methods... 165 | unless full then 166 | the_methods -= Object.instance_methods(true) 167 | the_methods -= Kernel.methods # FIX (true) - check 1.6 vs 1.8 168 | end 169 | 170 | the_methods.each do |meth| 171 | klassmethods[meth.to_s] = true 172 | end 173 | end 174 | end 175 | return klassmethods 176 | end 177 | 178 | # Check the class klass is a testing class 179 | # (by inspecting its name). 180 | def is_test_class(klass) 181 | klass = klass.to_s 182 | klasspath = klass.split(/::/) 183 | a_bad_classpath = klasspath.find do |s| s !~ ($r ? /Test$/ : /^Test/) end 184 | return a_bad_classpath.nil? 185 | end 186 | 187 | # Generate the name of a testclass from non-test class 188 | # so that Foo::Blah => TestFoo::TestBlah, etc. It the 189 | # name is already a test class, convert it the other way. 190 | def convert_class_name(name) 191 | name = name.to_s 192 | 193 | if self.is_test_class(name) then 194 | if $r then 195 | name = name.gsub(/Test($|::)/, '\1') # FooTest::BlahTest => Foo::Blah 196 | else 197 | name = name.gsub(/(^|::)Test/, '\1') # TestFoo::TestBlah => Foo::Blah 198 | end 199 | else 200 | if $r then 201 | name = name.gsub(/($|::)/, 'Test\1') # Foo::Blah => FooTest::BlahTest 202 | else 203 | name = name.gsub(/(^|::)/, '\1Test') # Foo::Blah => TestFoo::TestBlah 204 | end 205 | end 206 | 207 | return name 208 | end 209 | 210 | # Does all the work of finding a class by name, 211 | # obtaining its methods and those of its superclass. 212 | # The full parameter determines if all the methods 213 | # including those of Object and mixed in modules 214 | # are obtained (true if they are, false by default). 215 | def process_class(klassname, full=false) 216 | klass = self.get_class(klassname) 217 | raise "Couldn't get class for #{klassname}" if klass.nil? 218 | klassname = klass.name # refetch to get full name 219 | 220 | is_test_class = self.is_test_class(klassname) 221 | target = is_test_class ? @test_klasses : @klasses 222 | 223 | # record public instance methods JUST in this class 224 | target[klassname] = self.get_methods_for(klass, full) 225 | 226 | # record ALL instance methods including superclasses (minus Object) 227 | # Only minus Object if full is true. 228 | @inherited_methods[klassname] = self.get_inherited_methods_for(klass, full) 229 | return klassname 230 | end 231 | 232 | # Work through files, collecting class names, method names 233 | # and assertions. Detects ZenTest (SKIP|FULL) comments 234 | # in the bodies of classes. 235 | # For each class a count of methods and test methods is 236 | # kept, and the ratio noted. 237 | def scan_files(*files) 238 | assert_count = Hash.new(0) 239 | method_count = Hash.new(0) 240 | klassname = nil 241 | 242 | files.each do |path| 243 | is_loaded = false 244 | 245 | # if reading stdin, slurp the whole thing at once 246 | file = (path == "-" ? $stdin.read : File.new(path)) 247 | 248 | file.each_line do |line| 249 | 250 | if klassname then 251 | case line 252 | when /^\s*def/ then 253 | method_count[klassname] += 1 254 | when /assert|flunk/ then 255 | assert_count[klassname] += 1 256 | end 257 | end 258 | 259 | if line =~ /^\s*(?:class|module)\s+([\w:]+)/ then 260 | klassname = $1 261 | 262 | if line =~ /\#\s*ZenTest SKIP/ then 263 | klassname = nil 264 | next 265 | end 266 | 267 | full = false 268 | if line =~ /\#\s*ZenTest FULL/ then 269 | full = true 270 | end 271 | 272 | unless is_loaded then 273 | unless path == "-" then 274 | self.load_file(path) 275 | else 276 | eval file, TOPLEVEL_BINDING 277 | end 278 | is_loaded = true 279 | end 280 | 281 | begin 282 | klassname = self.process_class(klassname, full) 283 | rescue 284 | puts "# Couldn't find class for name #{klassname}" 285 | next 286 | end 287 | 288 | # Special Case: ZenTest is already loaded since we are running it 289 | if klassname == "TestZenTest" then 290 | klassname = "ZenTest" 291 | self.process_class(klassname, false) 292 | end 293 | 294 | end # if /class/ 295 | end # IO.foreach 296 | end # files 297 | 298 | result = [] 299 | method_count.each_key do |classname| 300 | 301 | entry = {} 302 | 303 | next if is_test_class(classname) 304 | testclassname = convert_class_name(classname) 305 | a_count = assert_count[testclassname] 306 | m_count = method_count[classname] 307 | ratio = a_count.to_f / m_count.to_f * 100.0 308 | 309 | entry['n'] = classname 310 | entry['r'] = ratio 311 | entry['a'] = a_count 312 | entry['m'] = m_count 313 | 314 | result.push entry 315 | end 316 | 317 | sorted_results = result.sort { |a,b| b['r'] <=> a['r'] } 318 | 319 | @result.push sprintf("# %25s: %4s / %4s = %6s%%", "classname", "asrt", "meth", "ratio") 320 | sorted_results.each do |e| 321 | @result.push sprintf("# %25s: %4d / %4d = %6.2f%%", e['n'], e['a'], e['m'], e['r']) 322 | end 323 | end 324 | 325 | # Adds a missing method to the collected results. 326 | def add_missing_method(klassname, methodname) 327 | @result.push "# ERROR method #{klassname}\##{methodname} does not exist (1)" if $DEBUG and not $TESTING 328 | @error_count += 1 329 | @missing_methods[klassname][methodname] = true 330 | end 331 | 332 | # looks up the methods and the corresponding test methods 333 | # in the collection already built. To reduce duplication 334 | # and hide implementation details. 335 | def methods_and_tests(klassname, testklassname) 336 | return @klasses[klassname], @test_klasses[testklassname] 337 | end 338 | 339 | # Checks, for the given class klassname, that each method 340 | # has a corrsponding test method. If it doesn't this is 341 | # added to the information for that class 342 | def analyze_impl(klassname) 343 | testklassname = self.convert_class_name(klassname) 344 | if @test_klasses[testklassname] then 345 | _, testmethods = methods_and_tests(klassname, testklassname) 346 | 347 | # check that each method has a test method 348 | @klasses[klassname].each_key do | methodname | 349 | testmethodname = normal_to_test(methodname) 350 | unless testmethods[testmethodname] then 351 | begin 352 | unless testmethods.keys.find { |m| m =~ /#{testmethodname}(_\w+)+$/ } then 353 | self.add_missing_method(testklassname, testmethodname) 354 | end 355 | rescue RegexpError 356 | puts "# ERROR trying to use '#{testmethodname}' as a regex. Look at #{klassname}.#{methodname}" 357 | end 358 | end # testmethods[testmethodname] 359 | end # @klasses[klassname].each_key 360 | else # ! @test_klasses[testklassname] 361 | puts "# ERROR test class #{testklassname} does not exist" if $DEBUG 362 | @error_count += 1 363 | 364 | @klasses[klassname].keys.each do | methodname | 365 | self.add_missing_method(testklassname, normal_to_test(methodname)) 366 | end 367 | end # @test_klasses[testklassname] 368 | end 369 | 370 | # For the given test class testklassname, ensure that all 371 | # the test methods have corresponding (normal) methods. 372 | # If not, add them to the information about that class. 373 | def analyze_test(testklassname) 374 | klassname = self.convert_class_name(testklassname) 375 | 376 | # CUT might be against a core class, if so, slurp it and analyze it 377 | if $stdlib[klassname] then 378 | self.process_class(klassname, true) 379 | self.analyze_impl(klassname) 380 | end 381 | 382 | if @klasses[klassname] then 383 | methods, testmethods = methods_and_tests(klassname,testklassname) 384 | 385 | # check that each test method has a method 386 | testmethods.each_key do | testmethodname | 387 | if testmethodname =~ /^test_(?!integration_)/ then 388 | 389 | # try the current name 390 | methodname = test_to_normal(testmethodname, klassname) 391 | orig_name = methodname.dup 392 | 393 | found = false 394 | until methodname == "" or methods[methodname] or @inherited_methods[klassname][methodname] do 395 | # try the name minus an option (ie mut_opt1 -> mut) 396 | if methodname.sub!(/_[^_]+$/, '') then 397 | if methods[methodname] or @inherited_methods[klassname][methodname] then 398 | found = true 399 | end 400 | else 401 | break # no more substitutions will take place 402 | end 403 | end # methodname == "" or ... 404 | 405 | unless found or methods[methodname] or methodname == "initialize" then 406 | self.add_missing_method(klassname, orig_name) 407 | end 408 | 409 | else # not a test_.* method 410 | unless testmethodname =~ /^util_/ then 411 | puts "# WARNING Skipping #{testklassname}\##{testmethodname}" if $DEBUG 412 | end 413 | end # testmethodname =~ ... 414 | end # testmethods.each_key 415 | else # ! @klasses[klassname] 416 | puts "# ERROR class #{klassname} does not exist" if $DEBUG 417 | @error_count += 1 418 | 419 | @test_klasses[testklassname].keys.each do |testmethodname| 420 | @missing_methods[klassname][test_to_normal(testmethodname)] = true 421 | end 422 | end # @klasses[klassname] 423 | end 424 | 425 | # create a given method at a given 426 | # indentation. Returns an array containing 427 | # the lines of the method. 428 | def create_method(indentunit, indent, name) 429 | meth = [] 430 | meth.push indentunit*indent + "def #{name}" 431 | meth.last << "(*args)" unless name =~ /^test/ 432 | indent += 1 433 | meth.push indentunit*indent + "raise NotImplementedError, 'Need to write #{name}'" 434 | indent -= 1 435 | meth.push indentunit*indent + "end" 436 | return meth 437 | end 438 | 439 | # Walk each known class and test that each method has 440 | # a test method 441 | # Then do it in the other direction... 442 | def analyze 443 | # walk each known class and test that each method has a test method 444 | @klasses.each_key do |klassname| 445 | self.analyze_impl(klassname) 446 | end 447 | 448 | # now do it in the other direction... 449 | @test_klasses.each_key do |testklassname| 450 | self.analyze_test(testklassname) 451 | end 452 | end 453 | 454 | # Using the results gathered during analysis 455 | # generate skeletal code with methods raising 456 | # NotImplementedError, so that they can be filled 457 | # in later, and so the tests will fail to start with. 458 | def generate_code 459 | @result.unshift "# Code Generated by ZenTest v. #{VERSION}" 460 | 461 | if $DEBUG then 462 | @result.push "# found classes: #{@klasses.keys.join(', ')}" 463 | @result.push "# found test classes: #{@test_klasses.keys.join(', ')}" 464 | end 465 | 466 | if @missing_methods.size > 0 then 467 | @result.push "" 468 | if $t then 469 | @result.push "require 'test/unit/testcase'" 470 | @result.push "require 'test/unit' if $0 == __FILE__" 471 | else 472 | @result.push "require 'minitest/autorun'" 473 | end 474 | @result.push "" 475 | end 476 | 477 | indentunit = " " 478 | 479 | @missing_methods.keys.sort.each do |fullklasspath| 480 | methods = @missing_methods[fullklasspath] 481 | cls_methods = methods.keys.grep(/^(self\.|test_class_)/) 482 | methods.delete_if {|k,v| cls_methods.include? k } 483 | 484 | next if methods.empty? and cls_methods.empty? 485 | 486 | indent = 0 487 | is_test_class = self.is_test_class(fullklasspath) 488 | 489 | clsname = $t ? "Test::Unit::TestCase" : "Minitest::Test" 490 | superclass = is_test_class ? " < #{clsname}" : '' 491 | 492 | @result.push indentunit*indent + "class #{fullklasspath}#{superclass}" 493 | indent += 1 494 | 495 | meths = [] 496 | 497 | cls_methods.sort.each do |method| 498 | meth = create_method(indentunit, indent, method) 499 | meths.push meth.join("\n") 500 | end 501 | 502 | methods.keys.sort.each do |method| 503 | next if method =~ /pretty_print/ 504 | meth = create_method(indentunit, indent, method) 505 | meths.push meth.join("\n") 506 | end 507 | 508 | @result.push meths.join("\n\n") 509 | 510 | indent -= 1 511 | @result.push indentunit*indent + "end" 512 | @result.push '' 513 | end 514 | 515 | @result.push "# Number of errors detected: #{@error_count}" 516 | @result.push '' 517 | end 518 | 519 | # presents results in a readable manner. 520 | def result 521 | return @result.join("\n") 522 | end 523 | 524 | # Provide a certain amount of help. 525 | def self.usage 526 | puts <<-EO_USAGE 527 | usage: #{File.basename $0} [options] test-and-implementation-files... 528 | 529 | ZenTest scans your target and unit-test code and writes your missing 530 | code based on simple naming rules, enabling XP at a much quicker 531 | pace. ZenTest only works with Ruby and Minitest or Test::Unit. 532 | 533 | ZenTest uses the following rules to figure out what code should be 534 | generated: 535 | 536 | * Definition: 537 | * CUT = Class Under Test 538 | * TC = Test Class (for CUT) 539 | * TC's name is the same as CUT w/ "Test" prepended at every scope level. 540 | * Example: TestA::TestB vs A::B. 541 | * CUT method names are used in CT, with "test_" prependend and optional "_ext" extensions for differentiating test case edge boundaries. 542 | * Example: 543 | * A::B#blah 544 | * TestA::TestB#test_blah_normal 545 | * TestA::TestB#test_blah_missing_file 546 | * All naming conventions are bidirectional with the exception of test extensions. 547 | 548 | options: 549 | -h display this information 550 | -v display version information 551 | -r Reverse mapping (ClassTest instead of TestClass) 552 | -e (Rapid XP) eval the code generated instead of printing it 553 | -t test/unit generation (default is minitest). 554 | 555 | EO_USAGE 556 | end 557 | 558 | # Give help, then quit. 559 | def self.usage_with_exit 560 | self.usage 561 | exit 0 562 | end 563 | 564 | # Runs ZenTest over all the supplied files so that 565 | # they are analysed and the missing methods have 566 | # skeleton code written. 567 | # If no files are supplied, splutter out some help. 568 | def self.fix(*files) 569 | ZenTest.usage_with_exit if files.empty? 570 | zentest = ZenTest.new 571 | zentest.scan_files(*files) 572 | zentest.analyze 573 | zentest.generate_code 574 | return zentest.result 575 | end 576 | 577 | # Process all the supplied classes for methods etc, 578 | # and analyse the results. Generate the skeletal code 579 | # and eval it to put the methods into the runtime 580 | # environment. 581 | def self.autotest(*klasses) 582 | zentest = ZenTest.new 583 | klasses.each do |klass| 584 | zentest.process_class(klass) 585 | end 586 | 587 | zentest.analyze 588 | 589 | zentest.missing_methods.each do |klass,methods| 590 | methods.each do |method,x| 591 | warn "autotest generating #{klass}##{method}" 592 | end 593 | end 594 | 595 | zentest.generate_code 596 | code = zentest.result 597 | puts code if $DEBUG 598 | 599 | Object.class_eval code 600 | end 601 | end 602 | -------------------------------------------------------------------------------- /lib/zentest_mapping.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # ZenTestMapping - mapping method names from impl to test. 3 | # 4 | # Method names are mapped bidirectionally in the following way: 5 | # 6 | # method test_method 7 | # method? test_method_eh (too much exposure to Canadians :) 8 | # method! test_method_bang 9 | # method= test_method_equals 10 | # [] test_index 11 | # * test_times 12 | # == test_equals2 13 | # === test_equals3 14 | # 15 | # Further, any of the test methods should be able to have arbitrary 16 | # extensions put on the name to distinguish edge cases: 17 | # 18 | # method test_method 19 | # method test_method_simple 20 | # method test_method_no_network 21 | # 22 | # To allow for unmapped test methods (ie, non-unit tests), name them: 23 | # 24 | # test_integration_.* 25 | 26 | module ZenTestMapping 27 | 28 | @@orig_method_map = { 29 | '!' => 'bang', 30 | '%' => 'percent', 31 | '&' => 'and', 32 | '*' => 'times', 33 | '**' => 'times2', 34 | '+' => 'plus', 35 | '-' => 'minus', 36 | '/' => 'div', 37 | '<' => 'lt', 38 | '<=' => 'lte', 39 | '<=>' => 'spaceship', 40 | "<\<" => 'lt2', 41 | '==' => 'equals2', 42 | '===' => 'equals3', 43 | '=~' => 'equalstilde', 44 | '>' => 'gt', 45 | '>=' => 'ge', 46 | '>>' => 'gt2', 47 | '+@' => 'unary_plus', 48 | '-@' => 'unary_minus', 49 | '[]' => 'index', 50 | '[]=' => 'index_equals', 51 | '^' => 'carat', 52 | '|' => 'or', 53 | '~' => 'tilde', 54 | } 55 | 56 | @@method_map = @@orig_method_map.merge(@@orig_method_map.invert) 57 | 58 | @@mapped_re = @@orig_method_map.values.sort_by { |k| k.length }.map {|s| 59 | Regexp.escape(s) 60 | }.reverse.join("|") 61 | 62 | def munge name 63 | name = name.to_s.dup 64 | 65 | is_cls_method = name.sub!(/^self\./, '') 66 | 67 | name = @@method_map[name] if @@method_map.has_key? name 68 | name = name.sub(/=$/, '_equals') 69 | name = name.sub(/\?$/, '_eh') 70 | name = name.sub(/\!$/, '_bang') 71 | 72 | name = yield name if block_given? 73 | 74 | name = "class_" + name if is_cls_method 75 | 76 | name 77 | end 78 | 79 | # Generates a test method name from a normal method, 80 | # taking into account names composed of metacharacters 81 | # (used for arithmetic, etc 82 | def normal_to_test name 83 | "test_#{munge name}" 84 | end 85 | 86 | def unmunge name 87 | name = name.to_s.dup 88 | 89 | is_cls_method = name.sub!(/^class_/, '') 90 | 91 | name = name.sub(/_equals(_.*)?$/, '=') unless name =~ /index/ 92 | name = name.sub(/_bang(_.*)?$/, '!') 93 | name = name.sub(/_eh(_.*)?$/, '?') 94 | name = name.sub(/^(#{@@mapped_re})(_.*)?$/) {$1} 95 | name = yield name if block_given? 96 | name = @@method_map[name] if @@method_map.has_key? name 97 | name = 'self.' + name if is_cls_method 98 | 99 | name 100 | end 101 | 102 | # Converts a method name beginning with test to its 103 | # corresponding normal method name, taking into account 104 | # symbolic names which may have been anglicised by 105 | # #normal_to_test(). 106 | def test_to_normal(name, klassname=nil) 107 | unmunge(name.to_s.sub(/^test_/, '')) do |n| 108 | if defined? @inherited_methods then 109 | known_methods = (@inherited_methods[klassname] || {}).keys.sort.reverse 110 | known_methods_re = known_methods.map {|s| Regexp.escape(s) }.join("|") 111 | n = n.sub(/^(#{known_methods_re})(_.*)?$/) { $1 } unless 112 | known_methods_re.empty? 113 | n 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /test/test_focus.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'minitest/autorun' 3 | require 'focus' 4 | 5 | class TestFocus < Minitest::Test 6 | def setup 7 | @x = 1 8 | end 9 | 10 | def teardown 11 | assert_equal 2, @x 12 | end 13 | 14 | def test_focus 15 | @x += 1 16 | end 17 | 18 | def test_ignore1 19 | flunk "ignore me!" 20 | end 21 | 22 | def test_ignore2 23 | flunk "ignore me!" 24 | end 25 | 26 | def test_ignore3 27 | flunk "ignore me!" 28 | end 29 | 30 | def test_focus2 31 | @x += 1 32 | end 33 | 34 | focus :test_focus, :test_focus2 35 | end 36 | -------------------------------------------------------------------------------- /test/test_unit_diff.rb: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/ruby -w 2 | 3 | require 'rubygems' 4 | require 'minitest/autorun' 5 | 6 | require 'stringio' 7 | 8 | $TESTING = true 9 | 10 | require 'unit_diff' 11 | 12 | class TestUnitDiff < Minitest::Test 13 | 14 | def setup 15 | @diff = UnitDiff.new 16 | end 17 | 18 | def test_input 19 | header = "Loaded suite ./blah\nStarted\nFF\nFinished in 0.035332 seconds.\n\n" 20 | input = "#{header} 1) Failure:\ntest_test1(TestBlah) [./blah.rb:25]:\n<\"line1\\nline2\\nline3\\n\"> expected but was\n<\"line4\\nline5\\nline6\\n\">.\n\n 2) Failure:\ntest_test2(TestBlah) [./blah.rb:29]:\n<\"line1\"> expected but was\n<\"line2\\nline3\\n\\n\">.\n\n2 tests, 2 assertions, 2 failures, 0 errors\n" 21 | 22 | # TODO: I think I'd like a separate footer array as well 23 | expected = [[[" 1) Failure:\n", "test_test1(TestBlah) [./blah.rb:25]:\n", "<\"line1\\nline2\\nline3\\n\"> expected but was\n", "<\"line4\\nline5\\nline6\\n\">.\n"], 24 | [" 2) Failure:\n", "test_test2(TestBlah) [./blah.rb:29]:\n", "<\"line1\"> expected but was\n", "<\"line2\\nline3\\n\\n\">.\n"]], 25 | ["\n", "2 tests, 2 assertions, 2 failures, 0 errors\n"]] 26 | 27 | util_unit_diff(header, input, expected, :parse_input) 28 | end 29 | 30 | def test_input_miniunit 31 | header = "Loaded suite -e\nStarted\nF\nFinished in 0.035332 seconds.\n\n" 32 | input = "#{header} 1) Failure: 33 | test_blah(TestBlah) [./blah.rb:25]: 34 | Expected ['a', 'b', 'c'], not ['a', 'c', 'b']. 35 | 36 | 1 tests, 1 assertions, 1 failures, 0 errors 37 | " 38 | 39 | expected = [[[" 1) Failure:\n", 40 | "test_blah(TestBlah) [./blah.rb:25]:\n", 41 | "Expected ['a', 'b', 'c'], not ['a', 'c', 'b'].\n"]], 42 | ["\n", "1 tests, 1 assertions, 1 failures, 0 errors\n"]] 43 | 44 | util_unit_diff(header, input, expected, :parse_input) 45 | end 46 | 47 | def test_input_miniunit_multiline 48 | header = "Loaded suite -e\nStarted\nF\nFinished in 0.035332 seconds.\n\n" 49 | input = "#{header} 1) Failure: 50 | test_blah(TestBlah) [./blah.rb:25]: 51 | Expected ['a', 52 | 'b', 53 | 'c'], not ['a', 54 | 'c', 55 | 'b']. 56 | 57 | 1 tests, 1 assertions, 1 failures, 0 errors 58 | " 59 | 60 | expected = [[[" 1) Failure:\n", 61 | "test_blah(TestBlah) [./blah.rb:25]:\n", 62 | "Expected ['a',\n 'b',\n 'c'], not ['a',\n 'c',\n 'b'].\n"]], 63 | ["\n", "1 tests, 1 assertions, 1 failures, 0 errors\n"]] 64 | 65 | util_unit_diff(header, input, expected, :parse_input) 66 | end 67 | 68 | def test_input_mspec 69 | header = <<-HEADER 70 | Started 71 | .......F 72 | Finished in 0.1 seconds 73 | 74 | HEADER 75 | 76 | failure = <<-FAILURE 77 | 1) 78 | The unless expression should fail FAILED 79 | Expected nil to equal "baz": 80 | FAILURE 81 | 82 | backtrace = <<-BACKTRACE 83 | PositiveExpectation#== at spec/mspec.rb:217 84 | main.__script__ {} at spec/language/unless_spec.rb:49 85 | Proc#call at kernel/core/proc.rb:127 86 | SpecRunner#it at spec/mspec.rb:368 87 | main.it at spec/mspec.rb:412 88 | main.__script__ {} at spec/language/unless_spec.rb:48 89 | Proc#call at kernel/core/proc.rb:127 90 | SpecRunner#describe at spec/mspec.rb:378 91 | main.describe at spec/mspec.rb:408 92 | main.__script__ at spec/language/unless_spec.rb:3 93 | CompiledMethod#as_script at kernel/bootstrap/primitives.rb:41 94 | main.load at kernel/core/compile.rb:150 95 | main.__script__ {} at last_mspec.rb:11 96 | Array#each {} at kernel/core/array.rb:545 97 | Integer(Fixnum)#times at kernel/core/integer.rb:15 98 | Array#each at kernel/core/array.rb:545 99 | main.__script__ at last_mspec.rb:16 100 | CompiledMethod#as_script at kernel/bootstrap/primitives.rb:41 101 | main.load at kernel/core/compile.rb:150 102 | main.__script__ at kernel/loader.rb:145 103 | BACKTRACE 104 | 105 | footer = "\n8 examples, 1 failures\n" 106 | input = header + failure + backtrace + footer 107 | 108 | expected_backtrace = backtrace.split("\n").map {|l| "#{l}\n"} 109 | expected = [[["1)\n", "The unless expression should fail FAILED\n", 110 | "Expected nil to equal \"baz\":\n", 111 | *expected_backtrace]], 112 | ["\n", "8 examples, 1 failures\n"]] 113 | util_unit_diff(header, input, expected, :parse_input) 114 | end 115 | 116 | def test_input_mspec_multiline 117 | header = <<-HEADER 118 | Started 119 | .......F 120 | Finished in 0.1 seconds 121 | 122 | HEADER 123 | 124 | failure = <<-FAILURE 125 | 1) 126 | Compiler compiles a case without an argument FAILED 127 | Expected #], [:push_literal, "foo"], [:string_dup], [:goto, #