Compare commits

..

152 Commits

Author SHA1 Message Date
John Bintz 476491f2aa why is rake locked to that 2012-02-01 12:17:23 -05:00
John Bintz b25fe51eda only run ssh runners if there are files to run remotely, and fix kernel.select to work even better 2011-09-25 10:39:29 -04:00
John Bintz 5ef29ee43e even better timeout handling for ssh pipes 2011-09-25 10:11:06 -04:00
John Bintz 16809a69dc prevent catastrophic failures 2011-08-31 17:24:43 -04:00
John Bintz 95cbf09313 whoa all the tests pass 2011-08-31 15:12:47 -04:00
John Bintz 3f75c9fb76 even more test fixes, this almost works... 2011-08-31 14:40:58 -04:00
John Bintz 236de8be1a support providing a timeout to rsync for slower connections 2011-08-31 12:17:52 -04:00
John Bintz 265cb5021a more work on tests 2011-08-31 11:42:56 -04:00
John Bintz c88f16b7a2 make remote tasks a lot simpler 2011-08-31 07:00:22 -04:00
John Bintz 0685b5436f make config reading a global thing 2011-08-30 07:21:03 -04:00
John Bintz 06387cb1e6 use bundler, rails 3 style 2011-08-27 15:20:59 -04:00
John Bintz 94b73ee426 set an rsync timeout, and trying to get the tests to run 2011-08-23 07:01:48 -04:00
John Bintz ff803d6d3a obey rspec backtrace cleaner on errors 2011-08-07 13:03:36 -04:00
John Bintz bde81449dc gemfile 2011-08-05 15:40:39 -04:00
John Bintz b11f54e1d9 whatever 2011-08-04 20:57:52 -04:00
John Bintz e365c08940 rspec messages working now 2011-08-04 18:01:29 -04:00
John Bintz 890858e40b clear enough of rspec to work 2011-08-04 17:00:03 -04:00
John Bintz 1f5f494edc trap errors even better 2011-08-04 16:11:58 -04:00
John Bintz 02c050a9f4 trap errors even better 2011-08-04 16:09:28 -04:00
John Bintz 86d9ff7fc5 trap errors better 2011-08-04 16:06:28 -04:00
John Bintz c8ade6557e more modern rspec fixes 2011-08-04 16:03:08 -04:00
John Bintz 61eecdf2b8 make pending specs look like successes 2011-08-04 14:29:27 -04:00
John Bintz 15dbca2ee3 actually get it working with modern rspec 2011-08-04 13:36:48 -04:00
John Bintz 2c79435c2f move test/unit stuff elsewhere 2011-08-03 18:36:03 -04:00
Nick Gauthier f5c82a727a Merge branch 'master' of git://github.com/dereke/hydra into dereke-master
Conflicts:
	lib/hydra/master.rb
	lib/hydra/runner.rb
	lib/hydra/tasks.rb
	lib/hydra/worker.rb
2011-08-03 16:41:46 -04:00
Nick Gauthier d5a79b9b74 Merge pull request #51 from clemensp/master
add pass/fail info to hydra_heuristics.yml
2011-08-02 10:41:25 -07:00
Clemens Park eb0faf7514 add pass/fail info to hydra_heuristics.yml 2011-08-02 13:07:49 -04:00
Derek Ekins ee44109a1b properly failing the run if a cucumber file has an error 2011-07-28 12:19:47 +01:00
Derek Ekins 8fccb4132e added option to change output path of report 2011-07-19 09:38:19 +01:00
Derek Ekins a5ed377e13 improved the way that cucumber is called
no longer relying on internal api
2011-07-18 17:14:41 +01:00
Derek Ekins 455fa3674f results directory is now relative to directory tests are executed in 2011-07-18 10:29:50 +01:00
Derek Ekins b4efbd2d50 initial stab at providing pretty html reports
also added ability to provide cucumber options but is ugly at the moment
2011-07-15 15:51:44 +01:00
Nick Gauthier a9dbf8fda3 Merge pull request #45 from arturopie/runners-event
Runners event
2011-06-06 09:11:13 -07:00
Arturo Pie 5f0b8dc4a5 Added functionality to handle unexpected termination when using local worker 2011-06-06 11:44:12 -04:00
Arturo Pie 8a536781b2 Added a default runner log, and a test 2011-06-06 11:02:24 -04:00
Arturo Pie 0d4a1238f9 Implemented the runners log functionality. It needs to handle the case when an invalid path is given 2011-06-05 23:52:59 -04:00
Arturo Pie 49dfd644b8 Sig Hup was implemented 2011-06-03 18:51:36 -04:00
Arturo Pie d6ff3ea5d2 Some fixes to the tests and removed some commented-out code 2011-06-02 15:02:58 -04:00
Arturo Pie 5bd2e5f323 Fixed indentation in master_test and a runner test 2011-06-02 14:18:54 -04:00
Nick Gauthier f8e7b6d0db Merge pull request #44 from mirell/master
Utilize CUCUMBER_OPTS environment variable
2011-06-02 07:47:30 -07:00
Arturo Pie 692c8cf790 redirecting some ugly output when running tests 2011-06-01 17:29:45 -04:00
Mark A. Miller 5c68763612 Parse Cucumber environment configuration options 2011-06-01 16:03:37 -05:00
Arturo Pie 3a613ef1b2 Removed the class methods 2011-06-01 16:45:31 -04:00
Arturo Pie 7c001ab485 Runner events implemented. Added at_exit hook to make sure runner_end is fired on abnormal termination 2011-06-01 12:45:43 -04:00
Arturo Pie 2648c3679f Two runner events implemented and tested at runner level 2011-05-30 18:08:50 -04:00
Arturo Pie b51bb8c136 Some refactoring and added .rvmrc to .gitignore 2011-05-30 13:40:44 -04:00
Nick Gauthier 88c07f864f Merge pull request #40 from arturopie/master
added running time
2011-05-20 06:42:08 -07:00
Arturo Pie 139b96f434 Running time report was added 2011-05-19 16:42:57 -04:00
Nick Gauthier 0d1f0f5f9e Merged pull request #38 from sskirby/remote_command.
Remote command
2011-04-26 12:40:26 -07:00
Nick Gauthier 3fd2a7ae64 Merged pull request #37 from sskirby/stack_overflow.
Stack overflow
2011-04-26 12:39:07 -07:00
Arturo 3ca3f1cd24 Added test for RemoteTasks that run arbitrary commands 2011-04-26 15:33:14 -04:00
Sean Kirby beae81621d Added ability to run arbitrary commands on remote machines (useful for updating bundles!) 2011-04-26 12:18:21 -04:00
Arturo 78ae8eace6 Fixed stack overflow when runners produce a lot of non-Hydra console output. 2011-04-26 11:08:15 -04:00
Sean Kirby a9a0845b19 fixed stack overflows that could happen when a large number of runners run tests that output a lot to the console when ssh testing 2011-04-25 16:13:07 -04:00
Nick Gauthier b89a60d897 Version bump to 0.23.3 2011-03-22 19:26:49 -04:00
Nick Gauthier 430c7f9a59 Merge branch 'master' of git://github.com/Dishwasha/hydra into Dishwasha-master 2011-03-22 19:23:02 -04:00
Nick Gauthier 51af3385ea added gem statements so we don't need a gemset 2011-03-22 19:22:12 -04:00
Ethan Waldo 52e06ccffc Fixed issue where tasks weren't detecting rails environment. This caused cucumber tests to be run in test environment instead of cucumber. Figured the easiest thing to due was allow the environment to be specified in the rake task itself via t.environment = 2011-02-08 11:38:13 -06:00
Nick Gauthier 248221f5c7 removed warmsnake binary. moved to hydra-console gem 2010-11-03 10:25:13 -04:00
Nick Gauthier ba2d10c629 Version 0.23.2: SSH worker fixes. Thanks sskirby! 2010-11-03 09:18:06 -04:00
Nick Gauthier ca3bfd8543 Version bump to 0.23.2 2010-11-03 09:16:54 -04:00
Sean Kirby fbd1e9f6e2 Added more meaningful output when a test dies before it can be run 2010-10-29 00:23:57 -04:00
Sean Kirby db33cc8e56 Fixed runner crash if a test file cannot properly be required. This can
happen for instance when two test classes have the same name but different
super classes.
2010-10-29 00:23:57 -04:00
Nick Gauthier 9e449918a4 Version 0.23.1: fix to rspec autorun override 2010-10-25 14:23:46 -04:00
Nick Gauthier 5773f3f629 Version bump to 0.23.1 2010-10-25 14:15:18 -04:00
Nick Gauthier 80a119b350 fixed hydra rspec autorun override 2010-10-25 14:14:58 -04:00
Nick Gauthier e97c65ea78 Version 0.23.0: Cucumber 0.9.2 support 2010-10-22 16:14:43 -04:00
Nick Gauthier 29a3836cdf Version bump to 0.23.0 2010-10-22 16:11:38 -04:00
Nick Gauthier dd25eac997 bumped cucumber version requirement 2010-10-22 16:11:22 -04:00
Rob Aldred 3faf03e15b step mother is no more in cucumber 0.9.0
refactor the cucumber loading
2010-09-29 14:59:32 +01:00
Nick Gauthier 304fbb9c85 Merge branch 'master' of github.com:ngauthier/hydra
Conflicts:
	VERSION
	hydra.gemspec
2010-09-07 14:26:59 -04:00
Nick Gauthier d943670878 Version 0.21.1: RAILS_ENV deprecated 2010-09-07 14:25:12 -04:00
Nick Gauthier 0b6e5fb45a Version bump to 0.21.1 2010-09-07 14:24:52 -04:00
Nick Gauthier 5f4bf365ba using new Rails.env 2010-09-07 14:24:14 -04:00
Nick Gauthier 0e8c80b588 Version 0.22.1: Proper exit status returned 2010-09-02 16:34:03 -05:00
Nick Gauthier e77d1a9710 Version bump to 0.22.1 2010-09-02 16:33:09 -05:00
Nick Gauthier d47f53d897 access to master's failed files. proper exit status via rake. 2010-09-02 16:33:03 -05:00
Nick Gauthier fcad8a27cc Version 0.22.0: Worker preload file. OSX tmpdir fix. Thanks @weplay 2010-09-02 16:12:03 -05:00
Nick Gauthier 88053b32ae Version bump to 0.22.0 2010-09-02 16:11:32 -05:00
Nick Gauthier 3fd1401513 Merge branch 'weplay'
Conflicts:
	test/fixtures/write_file_alternate_spec.rb
	test/fixtures/write_file_spec.rb
2010-09-02 16:08:08 -05:00
Nick Gauthier b800ddfc20 updated license 2010-09-02 14:50:09 -05:00
Nick Gauthier 0aa2f1de7f warmsnake docs 2010-08-24 09:52:16 -04:00
Nick Gauthier a9b9b09853 cleaned up test suite. allowed warmsnake to be run 2010-08-24 09:50:25 -04:00
Nick Gauthier 4fa7cc2b69 Version 0.21.0: Rspec 2.0 beta support 2010-08-23 14:43:18 -04:00
Nick Gauthier d00d342cd7 Version bump to 0.21.0 2010-08-23 14:42:56 -04:00
Nick Gauthier 05410b1af5 rspec 2.0 support 2010-08-23 14:42:51 -04:00
Nick Gauthier ca4b97443e Version 0.20.0: Cucumber 0.8.5 support 2010-08-20 15:45:51 -04:00
Nick Gauthier 85bdc061a9 Version bump to 0.20.0 2010-08-20 15:44:10 -04:00
Nick Gauthier d4f1bf59d2 cucumber 0.8.5 and warmsnake (experimental) 2010-08-20 15:43:56 -04:00
Nick Gauthier cc23eb35c4 added in warmsnake binary spike. p.s.: lol. 2010-08-04 16:54:37 -04:00
Nick Gauthier 7fe96ecc70 Version 0.19.4: hydra's progressbar is now R-G colorblind friendly. Thanks @ccahoon 2010-07-08 10:58:32 -04:00
Nick Gauthier 973e529a86 Version bump to 0.19.4 2010-07-08 10:57:32 -04:00
Chris Cahoon 33ed55b432 Progress bar uses darker red and green.
This brings it in line with the red and green used in other
testing suites. It has the additional advantage of being
more useful to people with at least my particular brand
of color deficiency, which made it difficult to distinguish
the lighter red and green previously used.
2010-07-08 10:49:21 -04:00
Nick Gauthier 2b387cabb2 Version 0.19.3: hybrid task setup with t.serial 2010-07-08 10:46:14 -04:00
Nick Gauthier e488ed454d Version bump to 0.19.3 2010-07-08 10:45:37 -04:00
Nick Gauthier 5f55baba19 added t.serial task option. moved rails env check to task run instead of definition 2010-07-08 10:45:25 -04:00
Nick Gauthier 2774881e7d Version 0.19.2: removed glitchy incomplete file printout. updated therubyracer support 2010-06-18 13:56:32 -04:00
Nick Gauthier 66232c6634 Version bump to 0.19.2 2010-06-18 13:55:57 -04:00
Nick Gauthier da1d3f9d44 removed incomplete file dump since it glitches for local workers and runners. patched to work with latest therubyracer 2010-06-18 13:55:48 -04:00
Nick Gauthier c1ed4ec12a Version 0.19.1: added worker events. fixed deadlock bug. warn when env is development. 2010-06-18 13:39:16 -04:00
Nick Gauthier 3dbd30a80e Version bump to 0.19.1 2010-06-18 13:38:33 -04:00
Luke Melia c9b0c3215e Work around an issue where Dir.tmpdir under OS X behaves differently when in an SSH session and from the console 2010-06-09 17:11:49 -04:00
Luke Melia a131de53f1 Upon boot, hydra workers will now require hydra_worker_init.rb in the current working directory if it is present. 2010-06-09 17:11:44 -04:00
Nick Gauthier 585c40ad01 fixed worker test to recognize the worker boot message 2010-06-09 08:43:15 -04:00
Lee Bankewitz and Luke Melia c8091718eb introduce custom listeners for worker_begin/worker_end
Conflicts:

	lib/hydra/listener/abstract.rb
	lib/hydra/message/worker_messages.rb
2010-06-08 11:02:57 -04:00
Luke Melia e5633f42ac Fix a bug where a worker never receives another file after it reports a deadlock error. 2010-06-08 11:01:38 -04:00
Lee Bankewitz and Luke Melia dbfd9275c1 warn when trying to run Rails tests with RAILS_ENV=development 2010-06-08 11:00:52 -04:00
Nick Gauthier 6fbc582fe5 added new mysql deadlock message 2010-06-07 12:36:40 -04:00
Nick Gauthier ae2895b6fc Version 0.19.0
* Cleaned up synchronization (sskirby)
* ERB in YAML files (raldred)
* Fix to work with current therubyracer
2010-06-07 11:36:45 -04:00
Nick Gauthier 492a0c22f0 Version bump to 0.19.0 2010-06-07 11:36:10 -04:00
Nick Gauthier e1783268cb Merge branch 'master' of http://github.com/sskirby/hydra into sskirby 2010-06-07 11:22:15 -04:00
Nick Gauthier 501aeb61ac fixed rubyracer v8 binding to work with current version 2010-06-07 10:36:28 -04:00
Nick Gauthier d2a14be3e4 Merge branch 'master' of http://github.com/raldred/hydra into raldred 2010-06-07 09:39:06 -04:00
Nick Gauthier c08bf86e0a Version 0.18.0: JSLint support 2010-05-28 10:59:03 -04:00
Nick Gauthier b523822aad Version bump to 0.18.0 2010-05-28 10:58:06 -04:00
Nick Gauthier 727871117c cleaned up js output 2010-05-28 10:51:27 -04:00
Nick Gauthier a69ae2e4ab jslint on json data 2010-05-28 10:42:17 -04:00
Nick Gauthier c7d7be6ed2 testing javascript files with jslint 2010-05-28 10:38:58 -04:00
Rob Aldred c8e08fabf6 added YmlLoadError class which is raised when yml file cannot be parsed 2010-05-14 14:23:29 +01:00
Rob Aldred 89c16e11c4 use erb to parse the config file, useful for putting dynamic values
also mkdir -p the directory the ssh class cd's to, to avoid errors if the dir is not there.
2010-05-14 14:16:50 +01:00
Sean Kirby 3500388765 merged with ngauthier master 2010-05-10 16:23:27 -04:00
Sean Kirby d5ff415460 Version bump to 0.16.10 2010-05-10 16:06:33 -04:00
Sean Kirby 818711f165 removed debugging outputs 2010-05-10 16:05:45 -04:00
Sean Kirby 77b7b6b66f added more tracing information and properly lists untested files when shutdown prematurely 2010-05-10 15:59:16 -04:00
Nick Gauthier 969f834440 Version 0.17.0: Profiling support 2010-05-10 14:24:00 -04:00
Nick Gauthier c537319839 Version bump to 0.17.0 2010-05-10 14:22:35 -04:00
Nick Gauthier 4ad5a1ed3d added profiling tasks 2010-05-10 14:22:25 -04:00
Sean Kirby de3de0f4f1 Version bump to 0.1.1 2010-05-10 14:03:15 -04:00
Sean Kirby 2b7f36b470 Version bump to 0.1.0 2010-05-10 14:03:09 -04:00
Sean Kirby 6bfb03bbda Version bump to 0.0.0 2010-05-10 14:02:57 -04:00
Sean Kirby 7f8e573fb9 playing around with showing the list of untested files when the user hits ctrl-c 2010-05-07 19:15:14 -04:00
Sean Kirby 133ad90894 added ability to set the rails environment to run tests under 2010-05-07 17:06:13 -04:00
Sean Kirby aa4b315833 fixed tabs 2010-05-07 16:17:01 -04:00
Sean Kirby b8cff6023a added threading and ability to specify environment for remote tasks 2010-05-07 16:15:57 -04:00
Sean Kirby c281a82151 added ctags file to .gitignore 2010-05-07 14:45:14 -04:00
Sean Kirby 75cca89d3f refactored Sync object to own remote worker settings 2010-05-07 14:43:17 -04:00
Sean Kirby 7821852a52 refactored out the Rsync functionality of the Master into the Sync class and added a rake task to invoke syncing 2010-04-30 19:03:43 -04:00
Nick Gauthier 60c2d7344b Version 0.16.7: better mysql and postgres deadlock detection 2010-04-28 15:05:41 -04:00
Nick Gauthier a5a0c7ba42 Version bump to 0.16.7 2010-04-28 15:05:11 -04:00
Nick Gauthier 2f874f1f1b postgres and mysql specific deadlock detection 2010-04-28 15:05:03 -04:00
Nick Gauthier 42a3fa834b Version 0.16.6: Deadlock retry 2010-04-22 16:04:32 -04:00
Nick Gauthier bb70ebd611 Version bump to 0.16.6 2010-04-22 16:04:16 -04:00
Nick Gauthier 2750fa4aaa simple deadlock retry 2010-04-22 16:04:09 -04:00
Nick Gauthier 7853940813 Version 0.16.5: RSpec tests no longer fail when there is a pending spec. Thanks @rud! 2010-04-20 10:27:51 -04:00
Nick Gauthier dd1a27d382 Version bump to 0.16.5 2010-04-20 10:27:13 -04:00
Laust Rud Jacobsen f8cebe3b59 Bugfix: Rspec pending examples should not result in specs reported as failing
Refs http://github.com/ngauthier/hydra/issues/issue/7
2010-04-20 14:48:05 +02:00
Nick Gauthier 9068de745d Version 0.16.4: patch to safe_fork to catch when the user has no connection before forking 2010-04-19 13:10:29 -04:00
Nick Gauthier ed4265633a Version bump to 0.16.4 2010-04-19 13:10:13 -04:00
Nick Gauthier 361cd0724a patch to safe_fork to catch when the user has no connection before forking 2010-04-19 13:10:08 -04:00
Nick Gauthier 3c71985196 Version 0.16.3: ActiveRecord concurrency 2010-04-19 10:28:13 -04:00
Nick Gauthier 3926f1bf2e Version bump to 0.16.3 2010-04-19 10:27:49 -04:00
Nick Gauthier a6847107ad added activerecord concurrency to safe_fork 2010-04-19 10:27:44 -04:00
57 changed files with 7354 additions and 384 deletions

3
.gitignore vendored
View File

@ -17,5 +17,8 @@ tmtags
coverage coverage
rdoc rdoc
pkg pkg
tags
.rvmrc
hydra-runner.log
## PROJECT::SPECIFIC ## PROJECT::SPECIFIC

5
Gemfile Normal file
View File

@ -0,0 +1,5 @@
source :rubygems
gemspec
gem 'rake', '0.8.7'
gem 'test-unit', :require => 'test/unit'

48
Gemfile.lock Normal file
View File

@ -0,0 +1,48 @@
PATH
remote: .
specs:
hydra (0.23.3)
rake (= 0.8.7)
test-unit
GEM
remote: http://rubygems.org/
specs:
builder (2.1.2)
cucumber (0.9.2)
builder (~> 2.1.2)
diff-lcs (~> 1.1.2)
gherkin (~> 2.2.5)
json (~> 1.4.6)
term-ansicolor (~> 1.0.5)
diff-lcs (1.1.3)
gherkin (2.2.9)
json (~> 1.4.6)
term-ansicolor (~> 1.0.5)
json (1.4.6)
rake (0.8.7)
rspec (2.6.0)
rspec-core (~> 2.6.0)
rspec-expectations (~> 2.6.0)
rspec-mocks (~> 2.6.0)
rspec-core (2.6.4)
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
shoulda (2.10.3)
term-ansicolor (1.0.6)
test-unit (2.4.0)
therubyracer (0.7.4)
PLATFORMS
ruby
DEPENDENCIES
cucumber (= 0.9.2)
hydra!
rake (= 0.8.7)
rspec (~> 2.6.0)
rspec-core (= 2.6.4)
shoulda (= 2.10.3)
test-unit
therubyracer (= 0.7.4)

View File

@ -1,4 +1,4 @@
Copyright (c) 2009 Nick Gauthier Copyright (c) 2009-2010 Nick Gauthier
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@ -11,8 +11,10 @@ begin
gem.homepage = "http://github.com/ngauthier/hydra" gem.homepage = "http://github.com/ngauthier/hydra"
gem.authors = ["Nick Gauthier"] gem.authors = ["Nick Gauthier"]
gem.add_development_dependency "shoulda", "= 2.10.3" gem.add_development_dependency "shoulda", "= 2.10.3"
gem.add_development_dependency "rspec", "= 1.3.0" gem.add_development_dependency "rspec", "~> 2.6.0"
gem.add_development_dependency "cucumber", "= 0.6.4" gem.add_development_dependency "rspec-core", "= 2.6.4"
gem.add_development_dependency "cucumber", "= 0.9.2"
gem.add_development_dependency "therubyracer", "= 0.7.4"
end end
Jeweler::GemcutterTasks.new Jeweler::GemcutterTasks.new
rescue LoadError rescue LoadError
@ -21,7 +23,7 @@ end
require 'rake/testtask' require 'rake/testtask'
Rake::TestTask.new(:test) do |test| Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test' test.libs << 'test'
test.pattern = 'test/**/*_test.rb' test.pattern = 'test/**/*_test.rb'
test.verbose = true test.verbose = true
end end
@ -39,8 +41,6 @@ rescue LoadError
end end
end end
task :test => :check_dependencies
task :default => :test task :default => :test
require 'rake/rdoctask' require 'rake/rdoctask'

View File

@ -1 +1 @@
0.16.2 0.23.3

View File

@ -1,117 +1,191 @@
# Generated by jeweler # Generated by jeweler
# DO NOT EDIT THIS FILE DIRECTLY # DO NOT EDIT THIS FILE DIRECTLY
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = %q{hydra} s.name = %q{hydra}
s.version = "0.16.2" s.version = "0.23.3"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Nick Gauthier"] s.authors = [%q{Nick Gauthier}]
s.date = %q{2010-04-11} s.date = %q{2011-08-31}
s.description = %q{Spread your tests over multiple machines to test your code faster.} s.description = %q{Spread your tests over multiple machines to test your code faster.}
s.email = %q{nick@smartlogicsolutions.com} s.email = %q{nick@smartlogicsolutions.com}
s.extra_rdoc_files = [ s.extra_rdoc_files = [
"LICENSE", "LICENSE",
"README.rdoc", "README.rdoc",
"TODO" "TODO"
] ]
s.files = [ s.files = [
".document", ".document",
".gitignore", "Gemfile",
"LICENSE", "Gemfile.lock",
"README.rdoc", "LICENSE",
"Rakefile", "README.rdoc",
"TODO", "Rakefile",
"VERSION", "TODO",
"caliper.yml", "VERSION",
"hydra-icon-64x64.png", "caliper.yml",
"hydra.gemspec", "hydra-icon-64x64.png",
"hydra_gray.png", "hydra.gemspec",
"lib/hydra.rb", "hydra_gray.png",
"lib/hydra/cucumber/formatter.rb", "lib/hydra.rb",
"lib/hydra/hash.rb", "lib/hydra/config.rb",
"lib/hydra/listener/abstract.rb", "lib/hydra/cucumber/formatter.rb",
"lib/hydra/listener/minimal_output.rb", "lib/hydra/cucumber/partial_html.rb",
"lib/hydra/listener/notifier.rb", "lib/hydra/hash.rb",
"lib/hydra/listener/progress_bar.rb", "lib/hydra/js/lint.js",
"lib/hydra/listener/report_generator.rb", "lib/hydra/listener/abstract.rb",
"lib/hydra/master.rb", "lib/hydra/listener/cucumber.css",
"lib/hydra/message.rb", "lib/hydra/listener/cucumber_html_report.rb",
"lib/hydra/message/master_messages.rb", "lib/hydra/listener/jquery-min.js",
"lib/hydra/message/runner_messages.rb", "lib/hydra/listener/minimal_output.rb",
"lib/hydra/message/worker_messages.rb", "lib/hydra/listener/notifier.rb",
"lib/hydra/messaging_io.rb", "lib/hydra/listener/progress_bar.rb",
"lib/hydra/pipe.rb", "lib/hydra/listener/report_generator.rb",
"lib/hydra/runner.rb", "lib/hydra/master.rb",
"lib/hydra/safe_fork.rb", "lib/hydra/message.rb",
"lib/hydra/spec/autorun_override.rb", "lib/hydra/message/master_messages.rb",
"lib/hydra/spec/hydra_formatter.rb", "lib/hydra/message/runner_messages.rb",
"lib/hydra/ssh.rb", "lib/hydra/message/worker_messages.rb",
"lib/hydra/stdio.rb", "lib/hydra/messaging_io.rb",
"lib/hydra/tasks.rb", "lib/hydra/pipe.rb",
"lib/hydra/trace.rb", "lib/hydra/runner.rb",
"lib/hydra/worker.rb", "lib/hydra/runner_listener/abstract.rb",
"test/fixtures/assert_true.rb", "lib/hydra/safe_fork.rb",
"test/fixtures/config.yml", "lib/hydra/spec/autorun_override.rb",
"test/fixtures/features/step_definitions.rb", "lib/hydra/spec/hydra_formatter.rb",
"test/fixtures/features/write_alternate_file.feature", "lib/hydra/ssh.rb",
"test/fixtures/features/write_file.feature", "lib/hydra/stdio.rb",
"test/fixtures/hello_world.rb", "lib/hydra/sync.rb",
"test/fixtures/slow.rb", "lib/hydra/tasks.rb",
"test/fixtures/sync_test.rb", "lib/hydra/tmpdir.rb",
"test/fixtures/write_file.rb", "lib/hydra/trace.rb",
"test/fixtures/write_file_alternate_spec.rb", "lib/hydra/worker.rb",
"test/fixtures/write_file_spec.rb", "test/fixtures/assert_true.rb",
"test/master_test.rb", "test/fixtures/config.yml",
"test/message_test.rb", "test/fixtures/conflicting.rb",
"test/pipe_test.rb", "test/fixtures/features/step_definitions.rb",
"test/runner_test.rb", "test/fixtures/features/write_alternate_file.feature",
"test/ssh_test.rb", "test/fixtures/features/write_file.feature",
"test/test_helper.rb", "test/fixtures/hello_world.rb",
"test/worker_test.rb" "test/fixtures/hydra_worker_init.rb",
"test/fixtures/js_file.js",
"test/fixtures/json_data.json",
"test/fixtures/many_outputs_to_console.rb",
"test/fixtures/master_listeners.rb",
"test/fixtures/runner_listeners.rb",
"test/fixtures/slow.rb",
"test/fixtures/sync_test.rb",
"test/fixtures/task_test_config.yml",
"test/fixtures/write_file.rb",
"test/fixtures/write_file_alternate_spec.rb",
"test/fixtures/write_file_spec.rb",
"test/fixtures/write_file_with_pending_spec.rb",
"test/master_test.rb",
"test/message_test.rb",
"test/pipe_test.rb",
"test/runner_test.rb",
"test/ssh_test.rb",
"test/sync_test.rb",
"test/task_test.rb",
"test/test_helper.rb",
"test/worker_test.rb"
] ]
s.homepage = %q{http://github.com/ngauthier/hydra} s.homepage = %q{http://github.com/ngauthier/hydra}
s.rdoc_options = ["--charset=UTF-8"] s.require_paths = [%q{lib}]
s.require_paths = ["lib"] s.rubygems_version = %q{1.8.9}
s.rubygems_version = %q{1.3.6}
s.summary = %q{Distributed testing toolkit} s.summary = %q{Distributed testing toolkit}
s.test_files = [
"test/pipe_test.rb",
"test/test_helper.rb",
"test/ssh_test.rb",
"test/message_test.rb",
"test/master_test.rb",
"test/fixtures/write_file.rb",
"test/fixtures/slow.rb",
"test/fixtures/write_file_spec.rb",
"test/fixtures/features/step_definitions.rb",
"test/fixtures/hello_world.rb",
"test/fixtures/write_file_alternate_spec.rb",
"test/fixtures/sync_test.rb",
"test/fixtures/assert_true.rb",
"test/runner_test.rb",
"test/worker_test.rb"
]
if s.respond_to? :specification_version then if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3 s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<rake>)
s.add_runtime_dependency(%q<test-unit>, [">= 0"])
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"]) s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_development_dependency(%q<rspec>, ["= 1.3.0"]) s.add_development_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_development_dependency(%q<cucumber>, ["= 0.6.4"]) s.add_development_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_development_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_development_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_development_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_development_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_development_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_development_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_development_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_development_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_development_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_development_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_development_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_development_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_development_dependency(%q<therubyracer>, ["= 0.7.4"])
else else
s.add_dependency(%q<rake>)
s.add_dependency(%q<test-unit>, [">= 0"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"]) s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["= 1.3.0"]) s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<cucumber>, ["= 0.6.4"]) s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
end end
else else
s.add_dependency(%q<rake>)
s.add_dependency(%q<test-unit>, [">= 0"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"]) s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["= 1.3.0"]) s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<cucumber>, ["= 0.6.4"]) s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
end end
end end

View File

@ -7,9 +7,10 @@ require 'hydra/safe_fork'
require 'hydra/runner' require 'hydra/runner'
require 'hydra/worker' require 'hydra/worker'
require 'hydra/master' require 'hydra/master'
require 'hydra/sync'
require 'hydra/listener/abstract' require 'hydra/listener/abstract'
require 'hydra/listener/minimal_output' require 'hydra/listener/minimal_output'
require 'hydra/listener/report_generator' require 'hydra/listener/report_generator'
require 'hydra/listener/notifier' require 'hydra/listener/notifier'
require 'hydra/listener/progress_bar' require 'hydra/listener/progress_bar'
require 'hydra/runner_listener/abstract'

21
lib/hydra/config.rb Normal file
View File

@ -0,0 +1,21 @@
module Hydra
class Config
class << self
def load(config_file)
begin
config_erb = ERB.new(IO.read(config_file)).result(binding)
rescue Exception => e
raise(YmlLoadError,"config file was found, but could not be parsed with ERB.\n#{$!.inspect}")
end
begin
config_yml = YAML::load(config_erb)
rescue StandardError => e
raise(YmlLoadError,"config file was found, but could not be parsed.\n#{$!.inspect}")
end
config_yml
end
end
end
end

View File

@ -19,7 +19,6 @@ module Cucumber #:nodoc:
print_steps(:failed) print_steps(:failed)
print_snippets(@options) print_snippets(@options)
print_passing_wip(@options) print_passing_wip(@options)
print_tag_limit_warnings(features)
end end
# Removed all progress output # Removed all progress output

View File

@ -0,0 +1,24 @@
require 'cucumber/formatter/html'
module Hydra
module Formatter
class PartialHtml < Cucumber::Formatter::Html
def before_features(features)
# we do not want the default implementation as we will write our own header
end
def after_features(features)
# we do not want the default implementation as we will write our own footer
end
def after_step(step)
# need an alterantive way of incrementing progress/outputing stats
end
def percent_done
0
end
end
end
end

5150
lib/hydra/js/lint.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@ module Hydra #:nodoc:
module Listener #:nodoc: module Listener #:nodoc:
# Abstract listener that implements all the events # Abstract listener that implements all the events
# but does nothing. # but does nothing.
class Abstract class Abstract
# Create a new listener. # Create a new listener.
# #
# Output: The IO object for outputting any information. # Output: The IO object for outputting any information.
@ -10,14 +10,23 @@ module Hydra #:nodoc:
def initialize(output = $stdout) def initialize(output = $stdout)
@output = output @output = output
end end
# Fired when testing has started # Fired when testing has started
def testing_begin(files) def testing_begin(files)
end end
# Fired when testing finishes # Fired when testing finishes, after the workers shutdown
def testing_end def testing_end
end end
# Fired after runner processes have been started
def worker_begin(worker)
end
# Fired before shutting down the worker
def worker_end(worker)
end
# Fired when a file is started # Fired when a file is started
def file_begin(file) def file_begin(file)
end end

View File

@ -0,0 +1,279 @@
/* cucumber.css is generated from cucumber.sass */
/* Regenerate with rake sass */
body {
font-size: 0px;
color: white;
margin: 0px;
padding: 0px;
}
.cucumber, td, th {
font: normal 11px "Lucida Grande", Helvetica, sans-serif;
background: white;
color: black;
}
.cucumber #cucumber-header, td #cucumber-header, th #cucumber-header {
background: #65c400;
color: white;
height: 6em;
}
.cucumber #cucumber-header #expand-collapse p, td #cucumber-header #expand-collapse p, th #cucumber-header #expand-collapse p {
float: right;
margin: 0 0 0 10px;
}
.cucumber .scenario h3, td .scenario h3, th .scenario h3 {
font-size: 11px;
padding: 3px;
margin: 0;
background: #65c400;
color: white;
font-weight: bold;
}
.cucumber h1, td h1, th h1 {
margin: 0px 10px 0px 10px;
padding: 10px;
font-family: "Lucida Grande", Helvetica, sans-serif;
font-size: 2em;
position: absolute;
}
.cucumber h4, td h4, th h4 {
margin-bottom: 2px;
}
.cucumber div.feature, td div.feature, th div.feature {
padding: 2px;
margin: 0px 10px 5px 10px;
}
.cucumber div.examples, td div.examples, th div.examples {
padding: 0em 0em 0em 1em;
}
.cucumber .stats, td .stats, th .stats {
margin: 2em;
}
.cucumber .summary ul.features li, td .summary ul.features li, th .summary ul.features li {
display: inline;
}
.cucumber .step_name, td .step_name, th .step_name {
float: left;
}
.cucumber .step_file, td .step_file, th .step_file {
text-align: right;
color: #999999;
}
.cucumber .step_file a, td .step_file a, th .step_file a {
color: #999999;
}
.cucumber .scenario_file, td .scenario_file, th .scenario_file {
float: right;
color: #999999;
}
.cucumber .tag, td .tag, th .tag {
font-weight: bold;
color: #246ac1;
}
.cucumber .backtrace, td .backtrace, th .backtrace {
margin-top: 0;
margin-bottom: 0;
margin-left: 1em;
color: black;
}
.cucumber a, td a, th a {
text-decoration: none;
color: #be5c00;
}
.cucumber a:hover, td a:hover, th a:hover {
text-decoration: underline;
}
.cucumber a:visited, td a:visited, th a:visited {
font-weight: normal;
}
.cucumber a div.examples, td a div.examples, th a div.examples {
margin: 5px 0px 5px 15px;
color: black;
}
.cucumber .outline table, td .outline table, th .outline table {
margin: 0px 0px 5px 10px;
}
.cucumber table, td table, th table {
border-collapse: collapse;
}
.cucumber table td, td table td, th table td {
padding: 3px 3px 3px 5px;
}
.cucumber table td.failed, .cucumber table td.passed, .cucumber table td.skipped, .cucumber table td.pending, .cucumber table td.undefined, td table td.failed, td table td.passed, td table td.skipped, td table td.pending, td table td.undefined, th table td.failed, th table td.passed, th table td.skipped, th table td.pending, th table td.undefined {
padding-left: 18px;
padding-right: 10px;
}
.cucumber table td.failed, td table td.failed, th table td.failed {
border-left: 5px solid #c20000;
border-bottom: 1px solid #c20000;
background: #fffbd3;
color: #c20000;
}
.cucumber table td.passed, td table td.passed, th table td.passed {
border-left: 5px solid #65c400;
border-bottom: 1px solid #65c400;
background: #dbffb4;
color: #3d7700;
}
.cucumber table td.skipped, td table td.skipped, th table td.skipped {
border-left: 5px solid aqua;
border-bottom: 1px solid aqua;
background: #e0ffff;
color: #001111;
}
.cucumber table td.pending, td table td.pending, th table td.pending {
border-left: 5px solid #faf834;
border-bottom: 1px solid #faf834;
background: #fcfb98;
color: #131313;
}
.cucumber table td.undefined, td table td.undefined, th table td.undefined {
border-left: 5px solid #faf834;
border-bottom: 1px solid #faf834;
background: #fcfb98;
color: #131313;
}
.cucumber table td.message, td table td.message, th table td.message {
border-left: 5px solid aqua;
border-bottom: 1px solid aqua;
background: #e0ffff;
color: #001111;
}
.cucumber ol, td ol, th ol {
list-style: none;
margin: 0px;
padding: 0px;
}
.cucumber ol li.step, td ol li.step, th ol li.step {
padding: 3px 3px 3px 18px;
margin: 5px 0px 5px 5px;
}
.cucumber ol li, td ol li, th ol li {
margin: 0em 0em 0em 1em;
padding: 0em 0em 0em 0.2em;
}
.cucumber ol li span.param, td ol li span.param, th ol li span.param {
font-weight: bold;
}
.cucumber ol li.failed, td ol li.failed, th ol li.failed {
border-left: 5px solid #c20000;
border-bottom: 1px solid #c20000;
background: #fffbd3;
color: #c20000;
}
.cucumber ol li.passed, td ol li.passed, th ol li.passed {
border-left: 5px solid #65c400;
border-bottom: 1px solid #65c400;
background: #dbffb4;
color: #3d7700;
}
.cucumber ol li.skipped, td ol li.skipped, th ol li.skipped {
border-left: 5px solid aqua;
border-bottom: 1px solid aqua;
background: #e0ffff;
color: #001111;
}
.cucumber ol li.pending, td ol li.pending, th ol li.pending {
border-left: 5px solid #faf834;
border-bottom: 1px solid #faf834;
background: #fcfb98;
color: #131313;
}
.cucumber ol li.undefined, td ol li.undefined, th ol li.undefined {
border-left: 5px solid #faf834;
border-bottom: 1px solid #faf834;
background: #fcfb98;
color: #131313;
}
.cucumber ol li.message, td ol li.message, th ol li.message {
border-left: 5px solid aqua;
border-bottom: 1px solid aqua;
background: #e0ffff;
color: #001111;
margin-left: 10px;
}
.cucumber #summary, td #summary, th #summary {
margin: 0px;
padding: 5px 10px;
text-align: right;
top: 0px;
right: 0px;
float: right;
}
.cucumber #summary p, td #summary p, th #summary p {
margin: 0 0 0 2px;
}
.cucumber #summary #totals, td #summary #totals, th #summary #totals {
font-size: 1.2em;
}
.ruby {
font-size: 12px;
font-family: monospace;
color: white;
background: black;
padding: 0.1em 0 0.2em 0;
}
.ruby .keyword {
color: #ff6600;
}
.ruby .constant {
color: #339999;
}
.ruby .attribute {
color: white;
}
.ruby .global {
color: white;
}
.ruby .module {
color: white;
}
.ruby .class {
color: white;
}
.ruby .string {
color: #66ff00;
}
.ruby .ident {
color: white;
}
.ruby .method {
color: #ffcc00;
}
.ruby .number {
color: white;
}
.ruby .char {
color: white;
}
.ruby .comment {
color: #9933cc;
}
.ruby .symbol {
color: white;
}
.ruby .regex {
color: #44b4cc;
}
.ruby .punct {
color: white;
}
.ruby .escape {
color: white;
}
.ruby .interp {
color: white;
}
.ruby .expr {
color: white;
}
.ruby .offending {
background: #333333;
}
.ruby .linenum {
width: 75px;
padding: 0.1em 1em 0.2em 0;
color: black;
background: #fffbd3;
}

View File

@ -0,0 +1,148 @@
require 'cucumber/formatter/ordered_xml_markup'
module Hydra #:nodoc:
module Listener #:nodoc:
# Output a textual report at the end of testing
class CucumberHtmlReport < Hydra::Listener::Abstract
def testing_end
CombineHtml.new.generate
end
end
class CombineHtml
def initialize(output_file = nil)
@results_path = File.join(Dir.pwd, 'results')
output_file = File.join(@results_path, 'html/index.html') if output_file.nil?
@io = File.open(output_file, "w")
@builder = create_builder(@io)
end
def generate
before_features
combine_features
after_features
@io.flush
@io.close
FileUtils.rm_r File.join(@results_path, 'features')
end
def wait_for_two_seconds_while_files_are_written
sleep 2
end
def combine_features
wait_for_two_seconds_while_files_are_written
Dir.glob(File.join(@results_path, 'features/*.html')).sort.each do |feature|
File.open( feature, "rb") do |f|
f.each_line do |line|
@builder << line
end
end
end
end
def before_features
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
@builder.declare!(
:DOCTYPE,
:html,
:PUBLIC,
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
)
@builder << '<html xmlns ="http://www.w3.org/1999/xhtml">'
@builder.head do
@builder.meta(:content => 'text/html;charset=utf-8')
@builder.title 'Cucumber'
inline_css
inline_js
end
@builder << '<body>'
#@builder << "<!-- Step count #{@step_count}-->"
@builder << '<div class="cucumber">'
@builder.div(:id => 'cucumber-header') do
@builder.div(:id => 'label') do
@builder.h1('Cucumber Features')
end
@builder.div(:id => 'summary') do
@builder.p('',:id => 'totals')
@builder.p('',:id => 'duration')
@builder.div(:id => 'expand-collapse') do
@builder.p('Expand All', :id => 'expander')
@builder.p('Collapse All', :id => 'collapser')
end
end
end
end
def after_features
@builder << '</div>'
@builder << '</body>'
@builder << '</html>'
end
def inline_css
@builder.style(:type => 'text/css') do
@builder << File.read(File.dirname(__FILE__) + '/cucumber.css')
end
end
def inline_js
@builder.script(:type => 'text/javascript') do
@builder << inline_jquery
@builder << inline_js_content
end
end
def inline_jquery
File.read(File.dirname(__FILE__) + '/jquery-min.js')
end
def inline_js_content
<<-EOF
SCENARIOS = "h3[id^='scenario_']";
$(document).ready(function() {
$(SCENARIOS).css('cursor', 'pointer');
$(SCENARIOS).click(function() {
$(this).siblings().toggle(250);
});
$("#collapser").css('cursor', 'pointer');
$("#collapser").click(function() {
$(SCENARIOS).siblings().hide();
});
$("#expander").css('cursor', 'pointer');
$("#expander").click(function() {
$(SCENARIOS).siblings().show();
});
})
function moveProgressBar(percentDone) {
$("cucumber-header").css('width', percentDone +"%");
}
function makeRed(element_id) {
$('#'+element_id).css('background', '#C40D0D');
$('#'+element_id).css('color', '#FFFFFF');
}
function makeYellow(element_id) {
$('#'+element_id).css('background', '#FAF834');
$('#'+element_id).css('color', '#000000');
}
EOF
end
def create_builder(io)
Cucumber::Formatter::OrderedXmlMarkup.new(:target => io, :indent => 0)
end
end
end
end

154
lib/hydra/listener/jquery-min.js vendored Normal file
View File

@ -0,0 +1,154 @@
/*!
* jQuery JavaScript Library v1.4.2
* http://jquery.com/
*
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2010, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Sat Feb 13 22:33:48 2010 -0500
*/
(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);

View File

@ -34,7 +34,7 @@ module Hydra #:nodoc:
complete = ((@files_completed.to_f / @total_files.to_f) * width).to_i complete = ((@files_completed.to_f / @total_files.to_f) * width).to_i
@output.write "\r" # move to beginning @output.write "\r" # move to beginning
@output.write 'Hydra Testing [' @output.write 'Hydra Testing ['
@output.write @errors ? "\033[1;31m" : "\033[1;32m" @output.write @errors ? "\033[0;31m" : "\033[0;32m"
complete.times{@output.write '#'} complete.times{@output.write '#'}
@output.write '>' @output.write '>'
(width-complete).times{@output.write ' '} (width-complete).times{@output.write ' '}

View File

@ -18,6 +18,7 @@ module Hydra #:nodoc:
def file_end(file, output) def file_end(file, output)
@report[file]['end'] = Time.now.to_f @report[file]['end'] = Time.now.to_f
@report[file]['duration'] = @report[file]['end'] - @report[file]['start'] @report[file]['duration'] = @report[file]['end'] - @report[file]['start']
@report[file]['all_tests_passed_last_run'] = (output == '.')
end end
# output the report # output the report
@ -28,3 +29,5 @@ module Hydra #:nodoc:
end end
end end
end end

View File

@ -1,15 +1,22 @@
require 'hydra/hash' require 'hydra/hash'
require 'hydra/config'
require 'open3' require 'open3'
require 'tmpdir' require 'hydra/tmpdir'
require 'erb'
require 'yaml' require 'yaml'
module Hydra #:nodoc: module Hydra #:nodoc:
# Hydra class responsible for delegate work down to workers. # Hydra class responsible for delegate work down to workers.
# #
# The Master is run once for any given testing session. # The Master is run once for any given testing session.
class YmlLoadError < StandardError; end
class Master class Master
include Hydra::Messages::Master include Hydra::Messages::Master
include Open3 include Open3
traceable('MASTER') traceable('MASTER')
attr_reader :failed_files
# Create a new Master # Create a new Master
# #
# Options: # Options:
@ -31,11 +38,14 @@ module Hydra #:nodoc:
opts.stringify_keys! opts.stringify_keys!
config_file = opts.delete('config') { nil } config_file = opts.delete('config') { nil }
if config_file if config_file
opts.merge!(YAML.load_file(config_file).stringify_keys!) config_yml = Hydra::Config.load(config_file)
opts.merge!(config_yml.stringify_keys!)
end end
@files = Array(opts.fetch('files') { nil }) @files = Array(opts.fetch('files') { nil })
raise "No files, nothing to do" if @files.empty? raise "No files, nothing to do" if @files.empty?
@incomplete_files = @files.dup @incomplete_files = @files.dup
@failed_files = []
@workers = [] @workers = []
@listeners = [] @listeners = []
@event_listeners = Array(opts.fetch('listeners') { nil } ) @event_listeners = Array(opts.fetch('listeners') { nil } )
@ -44,18 +54,36 @@ module Hydra #:nodoc:
listener = eval(l) listener = eval(l)
@event_listeners << listener if listener.is_a?(Hydra::Listener::Abstract) @event_listeners << listener if listener.is_a?(Hydra::Listener::Abstract)
end end
@string_runner_event_listeners = Array( opts.fetch( 'runner_listeners' ) { nil } )
@runner_log_file = opts.fetch('runner_log_file') { nil }
@verbose = opts.fetch('verbose') { false } @verbose = opts.fetch('verbose') { false }
@autosort = opts.fetch('autosort') { true } @autosort = opts.fetch('autosort') { true }
@sync = opts.fetch('sync') { nil } @sync = opts.fetch('sync') { nil }
@environment = opts.fetch('environment') { 'test' } || 'test'
@options = opts.fetch('options') { '' }
if @autosort if @autosort
sort_files_from_report sort_files_from_report
@event_listeners << Hydra::Listener::ReportGenerator.new(File.new(heuristic_file, 'w')) @event_listeners << Hydra::Listener::ReportGenerator.new(File.new(heuristic_file, 'w'))
end end
# default is one worker that is configured to use a pipe with one runner # default is one worker that is configured to use a pipe with one runner
worker_cfg = opts.fetch('workers') { [ { 'type' => 'local', 'runners' => 1} ] } worker_cfg = opts.fetch('workers') { [ { 'type' => 'local', 'runners' => 1} ] }
# if the number of files to run is equal to or less than the count of local
# runners, don't even bother with retmote workers.
locals, sshes = worker_cfg.partition { |l| l['type'] == 'local' }
if !locals.empty?
if @files.length <= locals.first['runners']
sshes = []
end
end
worker_cfg = locals + sshes
trace "Initialized" trace "Initialized"
trace " Files: (#{@files.inspect})" trace " Files: (#{@files.inspect})"
trace " Workers: (#{worker_cfg.inspect})" trace " Workers: (#{worker_cfg.inspect})"
@ -68,8 +96,11 @@ module Hydra #:nodoc:
end end
# Message handling # Message handling
def worker_begin(worker)
# Send a file down to a worker. @event_listeners.each {|l| l.worker_begin(worker) }
end
# Send a file down to a worker.
def send_file(worker) def send_file(worker)
f = @files.shift f = @files.shift
if f if f
@ -83,13 +114,29 @@ module Hydra #:nodoc:
# Process the results coming back from the worker. # Process the results coming back from the worker.
def process_results(worker, message) def process_results(worker, message)
@incomplete_files.delete_at(@incomplete_files.index(message.file)) if message.output =~ /ActiveRecord::StatementInvalid(.*)[Dd]eadlock/ or
trace "#{@incomplete_files.size} Files Remaining" message.output =~ /PGError: ERROR(.*)[Dd]eadlock/ or
@event_listeners.each{|l| l.file_end(message.file, message.output) } message.output =~ /Mysql::Error: SAVEPOINT(.*)does not exist: ROLLBACK/ or
if @incomplete_files.empty? message.output =~ /Mysql::Error: Deadlock found/
shutdown_all_workers trace "Deadlock detected running [#{message.file}]. Will retry at the end"
else @files.push(message.file)
send_file(worker) send_file(worker)
else
@incomplete_files.delete_at(@incomplete_files.index(message.file))
trace "#{@incomplete_files.size} Files Remaining"
@event_listeners.each{|l| l.file_end(message.file, message.output) }
unless message.output == '.'
@failed_files << message.file
end
if @incomplete_files.empty?
@workers.each do |worker|
@event_listeners.each{|l| l.worker_end(worker) }
end
shutdown_all_workers
else
send_file(worker)
end
end end
end end
@ -97,7 +144,7 @@ module Hydra #:nodoc:
attr_reader :report_text attr_reader :report_text
private private
def boot_workers(workers) def boot_workers(workers)
trace "Booting #{workers.size} workers" trace "Booting #{workers.size} workers"
workers.each do |worker| workers.each do |worker|
@ -116,57 +163,39 @@ module Hydra #:nodoc:
def boot_local_worker(worker) def boot_local_worker(worker)
runners = worker.fetch('runners') { raise "You must specify the number of runners" } runners = worker.fetch('runners') { raise "You must specify the number of runners" }
trace "Booting local worker" trace "Booting local worker"
pipe = Hydra::Pipe.new pipe = Hydra::Pipe.new
child = SafeFork.fork do child = SafeFork.fork do
pipe.identify_as_child pipe.identify_as_child
Hydra::Worker.new(:io => pipe, :runners => runners, :verbose => @verbose) Hydra::Worker.new(:io => pipe, :runners => runners, :verbose => @verbose, :runner_listeners => @string_runner_event_listeners, :runner_log_file => @runner_log_file, :options => @options )
end end
pipe.identify_as_parent pipe.identify_as_parent
@workers << { :pid => child, :io => pipe, :idle => false, :type => :local } @workers << { :pid => child, :io => pipe, :idle => false, :type => :local }
end end
def boot_ssh_worker(worker) def boot_ssh_worker(worker)
runners = worker.fetch('runners') { raise "You must specify the number of runners" } sync = Sync.new(worker, @sync, @verbose)
connect = worker.fetch('connect') { raise "You must specify an SSH connection target" } if sync.result == 0
ssh_opts = worker.fetch('ssh_opts') { "" } runners = worker.fetch('runners') { raise "You must specify the number of runners" }
directory = worker.fetch('directory') { raise "You must specify a remote directory" } command = worker.fetch('command') {
command = worker.fetch('command') { %{RAILS_ENV=#{@environment} ruby -rrubygems -e "require \\"bundler/setup\\"; require \\"hydra\\"; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose}, :runner_listeners => \\"#{@string_runner_event_listeners}\\", :runner_log_file => \\"#{@runner_log_file}\\", :options => {} );"}
"ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose});\""
}
if @sync
@sync.stringify_keys!
trace "Synchronizing with #{connect}\n\t#{@sync.inspect}"
local_dir = @sync.fetch('directory') {
raise "You must specify a synchronization directory"
} }
exclude_paths = @sync.fetch('exclude') { [] }
exclude_opts = exclude_paths.inject(''){|memo, path| memo += "--exclude=#{path} "}
rsync_command = [ trace "Booting SSH worker"
'rsync', trace command
'-avz', ssh = Hydra::SSH.new("#{sync.ssh_opts} #{sync.connect}", sync.remote_dir, command, worker['timeout'])
'--delete', return { :io => ssh, :idle => false, :type => :ssh, :connect => sync.connect }
exclude_opts, else
File.expand_path(local_dir)+'/', false
"-e \"ssh #{ssh_opts}\"",
"#{connect}:#{directory}"
].join(" ")
trace rsync_command
trace `#{rsync_command}`
end end
trace "Booting SSH worker"
ssh = Hydra::SSH.new("#{ssh_opts} #{connect}", directory, command)
return { :io => ssh, :idle => false, :type => :ssh }
end end
def shutdown_all_workers def shutdown_all_workers
trace "Shutting down all workers" trace "Shutting down all workers"
@workers.each do |worker| @workers.each do |worker|
worker[:io].write(Shutdown.new) if worker[:io] worker[:io].write(Shutdown.new) if worker[:io]
worker[:io].close if worker[:io] worker[:io].close if worker[:io]
end end
@listeners.each{|t| t.exit} @listeners.each{|t| t.exit}
end end
@ -181,25 +210,31 @@ module Hydra #:nodoc:
trace "Listening to #{worker.inspect}" trace "Listening to #{worker.inspect}"
if worker.fetch('type') { 'local' }.to_s == 'ssh' if worker.fetch('type') { 'local' }.to_s == 'ssh'
worker = boot_ssh_worker(worker) worker = boot_ssh_worker(worker)
@workers << worker @workers << worker if worker
end end
while true if worker
begin dead_count = 0
message = worker[:io].gets while true
trace "got message: #{message}" begin
# if it exists and its for me. message = worker[:io].gets
# SSH gives us back echoes, so we need to ignore our own messages trace "got message: #{message}"
if message and !message.class.to_s.index("Worker").nil? # if it exists and its for me.
message.handle(self, worker) # SSH gives us back echoes, so we need to ignore our own messages
if message and !message.class.to_s.index("Worker").nil?
message.handle(self, worker)
else
dead_count += 1
raise IOError if dead_count > 100
end
rescue IOError
trace "lost Worker [#{worker.inspect}]"
Thread.exit
end end
rescue IOError
trace "lost Worker [#{worker.inspect}]"
Thread.exit
end end
end end
end end
end end
@listeners.each{|l| l.join} @listeners.each{|l| l.join}
@event_listeners.each{|l| l.testing_end} @event_listeners.each{|l| l.testing_end}
end end
@ -219,7 +254,7 @@ module Hydra #:nodoc:
end end
def heuristic_file def heuristic_file
@heuristic_file ||= File.join(Dir.tmpdir, 'hydra_heuristics.yml') @heuristic_file ||= File.join(Dir.consistent_tmpdir, 'hydra_heuristics.yml')
end end
end end
end end

View File

@ -8,6 +8,12 @@ module Hydra #:nodoc:
end end
end end
class WorkerBegin < Hydra::Message
def handle(master, worker)
master.worker_begin(worker)
end
end
# Message telling the Runner to run a file # Message telling the Runner to run a file
class RunFile < Hydra::Message class RunFile < Hydra::Message
# The file that should be run # The file that should be run

View File

@ -8,14 +8,21 @@ module Hydra #:nodoc:
# IO.gets # IO.gets
# => Hydra::Message # or subclass # => Hydra::Message # or subclass
def gets def gets
raise IOError unless @reader while true
message = @reader.gets begin
return nil unless message raise IOError unless @reader
return Message.build(eval(message.chomp)) message = nil
rescue SyntaxError, NameError if result = Kernel.select([@reader], [], [], @timeout)
# uncomment to help catch remote errors by seeing all traffic message = @reader.gets
#$stderr.write "Not a message: [#{message.inspect}]\n" end
return gets return Message.build(:class => Hydra::Messages::Master::Shutdown) if result == nil
return nil unless message
return Message.build(eval(message.chomp))
rescue SyntaxError, NameError, Errno::EBADF
# uncomment to help catch remote errors by seeing all traffic
#$stderr.write "Not a message: [#{message.inspect}]\n"
end
end
end end
# Write a Message to the output IO object. It will automatically # Write a Message to the output IO object. It will automatically

View File

@ -31,9 +31,10 @@ module Hydra #:nodoc:
class Pipe class Pipe
include Hydra::MessagingIO include Hydra::MessagingIO
# Creates a new uninitialized pipe pair. # Creates a new uninitialized pipe pair.
def initialize def initialize(timeout = nil)
@child_read, @parent_write = IO.pipe @child_read, @parent_write = IO.pipe
@parent_read, @child_write = IO.pipe @parent_read, @child_write = IO.pipe
@timeout = timeout
end end
# Identify this side of the pipe as the child. # Identify this side of the pipe as the child.

View File

@ -1,16 +1,3 @@
require 'minitest/unit'
require 'test/unit/assertions'
#require 'test/unit/testresult'
#Test::Unit.run = true
module MiniTest
class Unit
def run(args)
puts 'not running!'
return 0
end
end
end
module Hydra #:nodoc: module Hydra #:nodoc:
# Hydra class responsible for running test files. # Hydra class responsible for running test files.
# #
@ -22,14 +9,26 @@ module Hydra #:nodoc:
class Runner class Runner
include Hydra::Messages::Runner include Hydra::Messages::Runner
traceable('RUNNER') traceable('RUNNER')
DEFAULT_LOG_FILE = 'hydra-runner.log'
PING_COUNT_FAILURE_TIME = 5
WAIT_BETWEEN_PING = 0.1
# Boot up a runner. It takes an IO object (generally a pipe from its # Boot up a runner. It takes an IO object (generally a pipe from its
# parent) to send it messages on which files to execute. # parent) to send it messages on which files to execute.
def initialize(opts = {}) def initialize(opts = {})
@io = opts.fetch(:io) { raise "No IO Object" } redirect_output( opts.fetch( :runner_log_file ) { DEFAULT_LOG_FILE } )
@verbose = opts.fetch(:verbose) { false } reg_trap_sighup
$stdout.sync = true
trace 'Booted. Sending Request for file'
@io = opts.fetch(:io) { raise "No IO Object" }
@verbose = opts.fetch(:verbose) { false }
@event_listeners = Array( opts.fetch( :runner_listeners ) { nil } )
@options = opts.fetch(:options)
$stdout.sync = true
runner_begin
trace 'Booted. Sending Request for file'
@io.write RequestFile.new @io.write RequestFile.new
begin begin
process_messages process_messages
@ -39,15 +38,32 @@ module Hydra #:nodoc:
end end
end end
def reg_trap_sighup
for sign in [:SIGHUP, :INT]
trap sign do
stop
exit if @event_listeners.empty?
end
end
@runner_began = true
end
def runner_begin
trace "Firing runner_begin event"
@event_listeners.each {|l| l.runner_begin( self ) }
end
# Run a test file and report the results # Run a test file and report the results
def run_file(file) def run_file(file)
trace "Running file: #{file}" trace "Running file: #{file}"
output = "" output = ""
if file =~ /_spec.rb$/ if file =~ /_spec.rb$/i
output = run_rspec_file(file) output = run_rspec_file(file)
elsif file =~ /.feature$/ elsif file =~ /.feature$/i
output = run_cucumber_file(file) output = run_cucumber_file(file)
elsif file =~ /.js$/i or file =~ /.json$/i
output = run_javascript_file(file)
else else
output = run_test_unit_file(file) output = run_test_unit_file(file)
end end
@ -60,11 +76,17 @@ module Hydra #:nodoc:
# Stop running # Stop running
def stop def stop
@running = false runner_end if @runner_began
@runner_began = @running = false
end end
def puke(klass, name, exception) def runner_end
puts "puke! #{klass}, #{name}, #{exception}" trace "Ending runner #{self.inspect}"
@event_listeners.each {|l| l.runner_end( self ) }
end
def format_exception(ex)
"#{ex.class.name}: #{ex.message}\n #{ex.backtrace.join("\n ")}"
end end
private private
@ -82,46 +104,46 @@ module Hydra #:nodoc:
message.handle(self) message.handle(self)
else else
@io.write Ping.new @io.write Ping.new
sleep WAIT_BETWEEN_PING
end end
rescue IOError => ex rescue IOError => ex
trace "Runner lost Worker" trace "Runner lost Worker"
@running = false stop
end end
end end
end end
def format_ex_in_file(file, ex)
"Error in #{file}:\n #{format_exception(ex)}"
end
# Run all the Test::Unit Suites in a ruby file # Run all the Test::Unit Suites in a ruby file
def run_test_unit_file(file) def run_test_unit_file(file)
begin begin
gem 'test-unit'
require 'test/unit'
require 'test/unit/testresult'
Test::Unit.run = true
require file require file
rescue LoadError => ex rescue LoadError => ex
trace "#{file} does not exist [#{ex.to_s}]" trace "#{file} does not exist [#{ex.to_s}]"
return ex.to_s return ex.to_s
rescue Exception => ex
p ex
trace "Error requiring #{file} [#{ex.to_s}]"
return format_ex_in_file(file, ex)
end end
output = [] output = []
# @result = Test::Unit::TestResult.new @result = Test::Unit::TestResult.new
# @result.add_listener(Test::Unit::TestResult::FAULT) do |value| @result.add_listener(Test::Unit::TestResult::FAULT) do |value|
# output << value output << value
# end end
klasses = Runner.find_classes_in_file(file) klasses = Runner.find_classes_in_file(file)
puts "Klasses: #{klasses.inspect}"
begin begin
klasses.each{|klass| klasses.each{|klass| klass.suite.run(@result){|status, name| ;}}
#klass.suite.run(@result){|status, name| ;}
klass.test_suites.each do |suite|
suite.test_methods.each do |test|
inst = suite.new(test)
inst._assertions = 0
result = inst.run(self)
#puts result
end
end
}
rescue => ex rescue => ex
output << ex.to_s output << format_ex_in_file(file, ex)
end end
return output.join("\n") return output.join("\n")
@ -131,74 +153,111 @@ module Hydra #:nodoc:
def run_rspec_file(file) def run_rspec_file(file)
# pull in rspec # pull in rspec
begin begin
require 'spec' require 'rspec'
require 'hydra/spec/hydra_formatter'
# Ensure we override rspec's at_exit # Ensure we override rspec's at_exit
require 'hydra/spec/autorun_override' RSpec::Core::Runner.disable_autorun!
rescue LoadError => ex rescue LoadError => ex
return ex.to_s return ex.to_s
end end
hydra_output = StringIO.new @hydra_output ||= StringIO.new
Spec::Runner.options.instance_variable_set(:@formatters, [ @hydra_output.rewind
Spec::Runner::Formatter::HydraFormatter.new( @hydra_output.truncate(0)
Spec::Runner.options.formatter_options,
hydra_output
)
])
Spec::Runner.options.instance_variable_set(
:@example_groups, []
)
Spec::Runner.options.instance_variable_set(
:@files, [file]
)
Spec::Runner.options.instance_variable_set(
:@files_loaded, false
)
Spec::Runner.options.run_examples
hydra_output.rewind
output = hydra_output.read.chomp
output = "" if output.gsub("\n","") =~ /^\.*$/
return output config = [ file ]
RSpec.reset
begin
result = RSpec::Core::Runner.run(config, @hydra_output, @hydra_output)
rescue Exception => ex
return ex.to_s + "\n" + ex.backtrace.reject { |line| RSpec.configuration.cleaned_from_backtrace?(line) }.join("\n")
end
@hydra_output.rewind
return (result == 1) ? @hydra_output.read : ""
end end
# run all the scenarios in a cucumber feature file # run all the scenarios in a cucumber feature file
def run_cucumber_file(file) def run_cucumber_file(file)
files = [file]
dev_null = StringIO.new
hydra_response = StringIO.new hydra_response = StringIO.new
unless @step_mother options = @options if @options.is_a?(Array)
require 'cucumber' options = @options.split(' ') if @options.is_a?(String)
fork_id = fork do
files = [file]
dev_null = StringIO.new
args = [file, options].flatten.compact
hydra_response.puts args.inspect
results_directory = "#{Dir.pwd}/results/features"
FileUtils.mkdir_p results_directory
require 'cucumber/cli/main'
require 'hydra/cucumber/formatter' require 'hydra/cucumber/formatter'
@step_mother = Cucumber::StepMother.new require 'hydra/cucumber/partial_html'
@cuke_configuration = Cucumber::Cli::Configuration.new(dev_null, dev_null)
@cuke_configuration.parse!(['features']+files)
@step_mother.options = @cuke_configuration.options Cucumber.logger.level = Logger::INFO
@step_mother.log = @cuke_configuration.log
@step_mother.load_code_files(@cuke_configuration.support_to_load) cuke = Cucumber::Cli::Main.new(args, dev_null, dev_null)
@step_mother.after_configuration(@cuke_configuration) cuke.configuration.formats << ['Cucumber::Formatter::Hydra', hydra_response]
@step_mother.load_code_files(@cuke_configuration.step_defs_to_load)
html_output = cuke.configuration.formats.select{|format| format[0] == 'html'}
if html_output
cuke.configuration.formats.delete(html_output)
cuke.configuration.formats << ['Hydra::Formatter::PartialHtml', "#{results_directory}/#{file.split('/').last}.html"]
end
cuke_runtime = Cucumber::Runtime.new(cuke.configuration)
cuke_runtime.run!
exit 1 if cuke_runtime.results.failure?
end end
cuke_formatter = Cucumber::Formatter::Hydra.new( Process.wait fork_id
@step_mother, hydra_response, @cuke_configuration.options
)
cuke_runner ||= Cucumber::Ast::TreeWalker.new(
@step_mother, [cuke_formatter], @cuke_configuration.options, dev_null
)
@step_mother.visitor = cuke_runner
features = @step_mother.load_plain_text_features(files)
tag_excess = tag_excess(features, @cuke_configuration.options[:tag_expression].limits)
@cuke_configuration.options[:tag_excess] = tag_excess
cuke_runner.visit_features(features)
hydra_response.puts "." if not $?.exitstatus == 0
hydra_response.rewind hydra_response.rewind
return hydra_response.read
hydra_response.read
end
def run_javascript_file(file)
errors = []
require 'v8'
V8::Context.new do |context|
context.load(File.expand_path(File.join(File.dirname(__FILE__), 'js', 'lint.js')))
context['input'] = lambda{
File.read(file)
}
context['reportErrors'] = lambda{|js_errors|
js_errors.each do |e|
e = V8::To.rb(e)
errors << "\n\e[1;31mJSLINT: #{file}\e[0m"
errors << " Error at line #{e['line'].to_i + 1} " +
"character #{e['character'].to_i + 1}: \e[1;33m#{e['reason']}\e[0m"
errors << "#{e['evidence']}"
end
}
context.eval %{
JSLINT(input(), {
sub: true,
onevar: true,
eqeqeq: true,
plusplus: true,
bitwise: true,
regexp: true,
newcap: true,
immed: true,
strict: true,
rhino: true
});
reportErrors(JSLINT.errors);
}
end
if errors.empty?
return '.'
else
return errors.join("\n")
end
end end
# find all the test unit classes in a given file, so we can run their suites # find all the test unit classes in a given file, so we can run their suites
@ -222,11 +281,7 @@ module Hydra #:nodoc:
nil nil
end end
end end
puts "Pre klasses: #{klasses.inspect}" return klasses.select{|k| k.respond_to? 'suite'}
puts klasses
return klasses.select{|k| k.respond_to? 'test_suites'}
end end
# Yanked a method from Cucumber # Yanked a method from Cucumber
@ -240,5 +295,16 @@ module Hydra #:nodoc:
end end
end.compact end.compact
end end
def redirect_output file_name
begin
$stderr = $stdout = File.open(file_name, 'a')
rescue
# it should always redirect output in order to handle unexpected interruption
# successfully
$stderr = $stdout = File.open(DEFAULT_LOG_FILE, 'a')
end
end
end end
end end

View File

@ -0,0 +1,23 @@
module Hydra #:nodoc:
module RunnerListener #:nodoc:
# Abstract listener that implements all the events
# but does nothing.
class Abstract
# Create a new listener.
#
# Output: The IO object for outputting any information.
# Defaults to STDOUT, but you could pass a file in, or STDERR
def initialize(output = $stdout)
@output = output
end
# Fired by the runner just before requesting the first file
def runner_begin( runner )
end
# Fired by the runner just after stoping
def runner_end( runner )
end
end
end
end

View File

@ -2,13 +2,13 @@ class SafeFork
def self.fork def self.fork
begin begin
# remove our connection so it doesn't get cloned # remove our connection so it doesn't get cloned
ActiveRecord::Base.remove_connection if defined?(ActiveRecord) connection = ActiveRecord::Base.remove_connection if defined?(ActiveRecord)
# fork a process # fork a process
child = Process.fork do child = Process.fork do
begin begin
# create a new connection and perform the action # create a new connection and perform the action
begin begin
ActiveRecord::Base.establish_connection if defined?(ActiveRecord) ActiveRecord::Base.establish_connection((connection || {}).merge({:allow_concurrency => true})) if defined?(ActiveRecord)
rescue ActiveRecord::AdapterNotSpecified rescue ActiveRecord::AdapterNotSpecified
# AR was defined but we didn't have a connection # AR was defined but we didn't have a connection
end end
@ -21,7 +21,7 @@ class SafeFork
ensure ensure
# make sure we re-establish the connection before returning to the main instance # make sure we re-establish the connection before returning to the main instance
begin begin
ActiveRecord::Base.establish_connection if defined?(ActiveRecord) ActiveRecord::Base.establish_connection((connection || {}).merge({:allow_concurrency => true})) if defined?(ActiveRecord)
rescue ActiveRecord::AdapterNotSpecified rescue ActiveRecord::AdapterNotSpecified
# AR was defined but we didn't have a connection # AR was defined but we didn't have a connection
end end

View File

@ -1,12 +1,3 @@
if defined?(Spec) if defined?(RSpec)
module Spec RSpec::Core::Runner.disable_autorun!
module Runner
class << self
# stop the auto-run at_exit
def run
return 0
end
end
end
end
end end

View File

@ -1,11 +1,22 @@
require 'spec/runner/formatter/progress_bar_formatter' require 'rspec/core/formatters/progress_formatter'
module Spec module RSpec
module Runner module Core
module Formatter module Formatters
class HydraFormatter < ProgressBarFormatter class HydraFormatter < ProgressFormatter
def example_passed(example)
output.print "."
end
def example_failed(example)
output.print "F"
end
# Stifle the post-test summary # Stifle the post-test summary
def dump_summary(duration, example, failure, pending) def dump_summary(duration, example, failure, pending)
end end
# Stifle pending specs
def dump_pending
end
end end
end end
end end

View File

@ -25,16 +25,9 @@ module Hydra #:nodoc:
# Hydra::SSH.new('-p 3022 user@server.com', '/home/user/Desktop', 'ls -l') # Hydra::SSH.new('-p 3022 user@server.com', '/home/user/Desktop', 'ls -l')
# To connect to server.com as user on port 3022, then CD to their desktop, then # To connect to server.com as user on port 3022, then CD to their desktop, then
# list all the files. # list all the files.
def initialize(connection_options, directory, command) def initialize(connection_options, directory, command, timeout = nil)
@writer, @reader, @error = popen3("ssh -tt #{connection_options}") @timeout = timeout
@writer.write("cd #{directory}\n") @writer, @reader, @error = popen3(%{ssh -tt #{connection_options} 'mkdir -p #{directory} && cd #{directory} && #{command}'})
@writer.write(command+"\n")
end
# Close the SSH connection
def close
@writer.write "exit\n"
super
end end
end end
end end

107
lib/hydra/sync.rb Normal file
View File

@ -0,0 +1,107 @@
require 'yaml'
require 'hydra/config'
module Hydra #:nodoc:
# Hydra class responsible for delegate work down to workers.
#
# The Sync is run once for each remote worker.
class Sync
traceable('SYNC')
self.class.traceable('SYNC MANY')
attr_reader :connect, :ssh_opts, :remote_dir, :result
# Create a new Sync instance to rsync source from the local machine to a remote worker
#
# Arguments:
# * :worker_opts
# * A hash of the configuration options for a worker.
# * :sync
# * A hash of settings specifically for copying the source directory to be tested
# to the remote worked
# * :verbose
# * Set to true to see lots of Hydra output (for debugging)
def initialize(worker_opts, sync_opts, verbose = false)
worker_opts ||= {}
worker_opts.stringify_keys!
@verbose = verbose
@connect = worker_opts.fetch('connect') { raise "You must specify an SSH connection target" }
@ssh_opts = worker_opts.fetch('ssh_opts') { "" }
@remote_dir = worker_opts.fetch('directory') { raise "You must specify a remote directory" }
@timeout = worker_opts.fetch('timeout') { 2 }
@result = 0
return unless sync_opts
sync_opts.stringify_keys!
@local_dir = sync_opts.fetch('directory') { raise "You must specify a synchronization directory" }
@exclude_paths = sync_opts.fetch('exclude') { [] }
trace "Initialized"
trace " Worker: (#{worker_opts.inspect})"
trace " Sync: (#{sync_opts.inspect})"
sync
end
def sync
#trace "Synchronizing with #{connect}\n\t#{sync_opts.inspect}"
exclude_opts = @exclude_paths.inject(''){|memo, path| memo += "--exclude=#{path} "}
rsync_command = [
'rsync',
'-avz',
'--delete',
"--timeout=#{@timeout}",
exclude_opts,
File.expand_path(@local_dir)+'/',
"-e \"ssh #{@ssh_opts}\"",
"#{@connect}:#{@remote_dir}"
].join(" ")
trace rsync_command
trace `#{rsync_command}`
@result = $?.exitstatus
end
def self.sync_many opts
opts.stringify_keys!
config_file = opts.delete('config') { nil }
if config_file
config_yml = Hydra::Config.load(config_file)
opts.merge!(config_yml.stringify_keys!)
end
@verbose = opts.fetch('verbose') { false }
@sync = opts.fetch('sync') { {} }
workers_opts = opts.fetch('workers') { [] }
@remote_worker_opts = []
workers_opts.each do |worker_opts|
worker_opts.stringify_keys!
if worker_opts['type'].to_s == 'ssh'
@remote_worker_opts << worker_opts
end
end
trace "Initialized"
trace " Sync: (#{@sync.inspect})"
trace " Workers: (#{@remote_worker_opts.inspect})"
Thread.abort_on_exception = true
trace "Processing workers"
@listeners = []
@remote_worker_opts.each do |worker_opts|
@listeners << Thread.new do
begin
trace "Syncing #{worker_opts.inspect}"
Sync.new worker_opts, @sync, @verbose
rescue
trace "Syncing failed [#{worker_opts.inspect}]"
end
end
end
@listeners.each{|l| l.join}
end
end
end

View File

@ -1,10 +1,15 @@
require 'open3' require 'open3'
require 'hydra/config'
module Hydra #:nodoc: module Hydra #:nodoc:
# Hydra Task Common attributes and methods # Hydra Task Common attributes and methods
class Task class Task
# Name of the task. Default 'hydra' # Name of the task. Default 'hydra'
attr_accessor :name attr_accessor :name
# Command line options
attr_accessor :options
# Files to test. # Files to test.
# You can add files manually via: # You can add files manually via:
# t.files << [file1, file2, etc] # t.files << [file1, file2, etc]
@ -31,6 +36,20 @@ module Hydra #:nodoc:
# t.listeners << Hydra::Listener::Notifier.new # t.listeners << Hydra::Listener::Notifier.new
attr_accessor :listeners attr_accessor :listeners
# Set to true if you want to run this task only on the local
# machine with one runner. A "Safe Mode" for some test
# files that may not play nice with others.
attr_accessor :serial
attr_accessor :environment
# Set to false if you don't want to show the total running time
attr_accessor :show_time
# Set to a valid file path if you want to save the output of the runners
# in a log file
attr_accessor :runner_log_file
# #
# Search for the hydra config file # Search for the hydra config file
def find_config_file def find_config_file
@ -40,7 +59,7 @@ module Hydra #:nodoc:
return @config if File.exists?(@config) return @config if File.exists?(@config)
@config = nil @config = nil
end end
# Add files to test by passing in a string to be run through Dir.glob. # Add files to test by passing in a string to be run through Dir.glob.
# For example: # For example:
# #
@ -59,7 +78,7 @@ module Hydra #:nodoc:
# t.add_files 'test/integration/**/*_test.rb' # t.add_files 'test/integration/**/*_test.rb'
# t.verbose = false # optionally set to true for lots of debug messages # t.verbose = false # optionally set to true for lots of debug messages
# t.autosort = false # disable automatic sorting based on runtime of tests # t.autosort = false # disable automatic sorting based on runtime of tests
# end # end
class TestTask < Hydra::Task class TestTask < Hydra::Task
# Create a new HydraTestTask # Create a new HydraTestTask
@ -68,20 +87,30 @@ module Hydra #:nodoc:
@files = [] @files = []
@verbose = false @verbose = false
@autosort = true @autosort = true
@serial = false
@listeners = [Hydra::Listener::ProgressBar.new] @listeners = [Hydra::Listener::ProgressBar.new]
@show_time = true
@options = ''
yield self if block_given? yield self if block_given?
# Ensure we override rspec's at_exit # Ensure we override rspec's at_exit
require 'hydra/spec/autorun_override' if defined?(RSpec)
RSpec::Core::Runner.disable_autorun!
end
@config = find_config_file unless @serial
@config = find_config_file
end
@opts = { @opts = {
:verbose => @verbose, :verbose => @verbose,
:autosort => @autosort, :autosort => @autosort,
:files => @files, :files => @files,
:listeners => @listeners :listeners => @listeners,
:environment => @environment,
:runner_log_file => @runner_log_file,
:options => @options
} }
if @config if @config
@opts.merge!(:config => @config) @opts.merge!(:config => @config)
@ -97,8 +126,132 @@ module Hydra #:nodoc:
def define def define
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}") desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
task @name do task @name do
Hydra::Master.new(@opts) if Object.const_defined?('Rails') && Rails.env == 'development'
#exit(0) #bypass test on_exit output $stderr.puts %{WARNING: Rails Environment is "development". Make sure to set it properly (ex: "RAILS_ENV=test rake hydra")}
end
start = Time.now if @show_time
puts '********************'
puts @options.inspect
master = Hydra::Master.new(@opts)
$stdout.puts "\nFinished in #{'%.6f' % (Time.now - start)} seconds." if @show_time
unless master.failed_files.empty?
raise "Hydra: Not all tests passes"
end
end
end
end
# Define a test task that uses hydra to profile your test files
#
# Hydra::ProfileTask.new('hydra:prof') do |t|
# t.add_files 'test/unit/**/*_test.rb'
# t.add_files 'test/functional/**/*_test.rb'
# t.add_files 'test/integration/**/*_test.rb'
# t.generate_html = true # defaults to false
# t.generate_text = true # defaults to true
# end
class ProfileTask < Hydra::Task
# boolean: generate html output from ruby-prof
attr_accessor :generate_html
# boolean: generate text output from ruby-prof
attr_accessor :generate_text
# Create a new Hydra ProfileTask
def initialize(name = 'hydra:profile')
@name = name
@files = []
@verbose = false
@generate_html = false
@generate_text = true
yield self if block_given?
# Ensure we override rspec's at_exit
require 'hydra/spec/autorun_override'
@config = find_config_file
@opts = {
:verbose => @verbose,
:files => @files
}
define
end
private
# Create the rake task defined by this HydraTestTask
def define
desc "Hydra Test Profile" + (@name == :hydra ? "" : " for #{@name}")
task @name do
require 'ruby-prof'
RubyProf.start
runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
@files.each do |file|
$stdout.write runner.run_file(file)
$stdout.flush
end
$stdout.write "\nTests complete. Generating profiling output\n"
$stdout.flush
result = RubyProf.stop
if @generate_html
printer = RubyProf::GraphHtmlPrinter.new(result)
out = File.new("ruby-prof.html", 'w')
printer.print(out, :min_self => 0.05)
out.close
$stdout.write "Profiling data written to [ruby-prof.html]\n"
end
if @generate_text
printer = RubyProf::FlatPrinter.new(result)
out = File.new("ruby-prof.txt", 'w')
printer.print(out, :min_self => 0.05)
out.close
$stdout.write "Profiling data written to [ruby-prof.txt]\n"
end
end
end
end
# Define a sync task that uses hydra to rsync the source tree under test to remote workers.
#
# This task is very useful to run before a remote db:reset task to make sure the db/schema.rb
# file is up to date on the remote workers.
#
# Hydra::SyncTask.new('hydra:sync') do |t|
# t.verbose = false # optionally set to true for lots of debug messages
# end
class SyncTask < Hydra::Task
# Create a new SyncTestTask
def initialize(name = :sync)
@name = name
@verbose = false
yield self if block_given?
@config = find_config_file
@opts = {
:verbose => @verbose
}
@opts.merge!(:config => @config) if @config
define
end
private
# Create the rake task defined by this HydraSyncTask
def define
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
task @name do
Hydra::Sync.sync_many(@opts)
end end
end end
end end
@ -112,8 +265,9 @@ module Hydra #:nodoc:
include Open3 include Open3
# Create a new hydra remote task with the given name. # Create a new hydra remote task with the given name.
# The task will be named hydra:remote:<name> # The task will be named hydra:remote:<name>
def initialize(name) def initialize(name, command=nil)
@name = name @name = name
@command = command
yield self if block_given? yield self if block_given?
@config = find_config_file @config = find_config_file
if @config if @config
@ -127,34 +281,42 @@ module Hydra #:nodoc:
def define def define
desc "Run #{@name} remotely on all workers" desc "Run #{@name} remotely on all workers"
task "hydra:remote:#{@name}" do task "hydra:remote:#{@name}" do
config = YAML.load_file(@config) config = Hydra::Config.load(@config)
environment = config.fetch('environment') { 'test' }
workers = config.fetch('workers') { [] } workers = config.fetch('workers') { [] }
workers = workers.select{|w| w['type'] == 'ssh'} workers = workers.select{|w| w['type'] == 'ssh'}
@command = "RAILS_ENV=#{environment} rake #{@name}" unless @command
$stdout.write "==== Hydra Running #{@name} ====\n"
Thread.abort_on_exception = true
@listeners = []
@results = {}
workers.each do |worker| workers.each do |worker|
$stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n" @listeners << Thread.new do
ssh_opts = worker.fetch('ssh_opts') { '' } begin
writer, reader, error = popen3("ssh -tt #{ssh_opts} #{worker['connect']} ") @results[worker] = if run_command(worker, @command)
writer.write("cd #{worker['directory']}\n") "==== #{@name} passed on #{worker['connect']} ====\n"
writer.write "echo BEGIN HYDRA\n" else
writer.write("RAILS_ENV=test rake #{@name}\n") "==== #{@name} failed on #{worker['connect']} ====\nPlease see above for more details.\n"
writer.write "echo END HYDRA\n" end
writer.write("exit\n") rescue
writer.close @results[worker] = "==== #{@name} failed for #{worker['connect']} ====\n#{$!.inspect}\n#{$!.backtrace.join("\n")}"
ignoring = true
while line = reader.gets
line.chomp!
if line =~ /echo END HYDRA$/
ignoring = true
end
$stdout.write "#{line}\n" unless ignoring
if line == 'BEGIN HYDRA'
ignoring = false
end end
end end
$stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n"
end end
@listeners.each{|l| l.join}
$stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n"
$stdout.write @results.values.join("\n")
end end
end end
def run_command worker, command
$stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n"
ssh_opts = worker.fetch('ssh_opts') { '' }
system %{ssh -tt #{ssh_opts} #{worker['connect']} 'cd #{worker['directory']} && #{command}'}
$?.exitstatus == 0
end
end end
# A Hydra global task is a task that is run both locally and remotely. # A Hydra global task is a task that is run both locally and remotely.
@ -164,7 +326,7 @@ module Hydra #:nodoc:
# Hydra::GlobalTask.new('db:reset') # Hydra::GlobalTask.new('db:reset')
# #
# Allows you to run: # Allows you to run:
# #
# rake hydra:db:reset # rake hydra:db:reset
# #
# Then, db:reset will be run locally and on all remote workers. This # Then, db:reset will be run locally and on all remote workers. This
@ -186,7 +348,7 @@ module Hydra #:nodoc:
def define def define
Hydra::RemoteTask.new(@name) Hydra::RemoteTask.new(@name)
desc "Run #{@name.to_s} Locally and Remotely across all Workers" desc "Run #{@name.to_s} Locally and Remotely across all Workers"
task "hydra:#{@name.to_s}" => [@name.to_s, "hydra:remote:#{@name.to_s}"] task "hydra:#{@name.to_s}" => [@name.to_s, "hydra:remote:#{@name.to_s}"]
end end
end end
end end

11
lib/hydra/tmpdir.rb Normal file
View File

@ -0,0 +1,11 @@
require 'tmpdir'
class Dir
def self.consistent_tmpdir
if RUBY_PLATFORM =~ /darwin/i
'/tmp' # OS X normally returns a crazy tmpdir, BUT when logged in via SSH, it is '/tmp'. This unifies it.
else
Dir.tmpdir
end
end
end

View File

@ -9,6 +9,8 @@ module Hydra #:nodoc:
class Worker class Worker
include Hydra::Messages::Worker include Hydra::Messages::Worker
traceable('WORKER') traceable('WORKER')
attr_reader :runners
# Create a new worker. # Create a new worker.
# * io: The IO object to use to communicate with the master # * io: The IO object to use to communicate with the master
# * num_runners: The number of runners to launch # * num_runners: The number of runners to launch
@ -17,16 +19,37 @@ module Hydra #:nodoc:
@io = opts.fetch(:io) { raise "No IO Object" } @io = opts.fetch(:io) { raise "No IO Object" }
@runners = [] @runners = []
@listeners = [] @listeners = []
@options = opts.fetch(:options)
load_worker_initializer
@runner_event_listeners = Array(opts.fetch(:runner_listeners) { nil })
@runner_event_listeners.select{|l| l.is_a? String}.each do |l|
@runner_event_listeners.delete_at(@runner_event_listeners.index(l))
listener = eval(l)
@runner_event_listeners << listener if listener.is_a?(Hydra::RunnerListener::Abstract)
end
@runner_log_file = opts.fetch(:runner_log_file) { nil }
boot_runners(opts.fetch(:runners) { 1 }) boot_runners(opts.fetch(:runners) { 1 })
@io.write(Hydra::Messages::Worker::WorkerBegin.new)
process_messages process_messages
@runners.each{|r| Process.wait r[:pid] } @runners.each{|r| Process.wait r[:pid] }
end end
def load_worker_initializer
# message handling methods if File.exist?('./hydra_worker_init.rb')
trace('Requiring hydra_worker_init.rb')
require 'hydra_worker_init'
else
trace('hydra_worker_init.rb not present')
end
end
# message handling methods
# When a runner wants a file, it hits this method with a message. # When a runner wants a file, it hits this method with a message.
# Then the worker bubbles the file request up to the master. # Then the worker bubbles the file request up to the master.
def request_file(message, runner) def request_file(message, runner)
@ -68,9 +91,10 @@ module Hydra #:nodoc:
trace "Booting #{num_runners} Runners" trace "Booting #{num_runners} Runners"
num_runners.times do num_runners.times do
pipe = Hydra::Pipe.new pipe = Hydra::Pipe.new
child = SafeFork.fork do child = SafeFork.fork do
pipe.identify_as_child pipe.identify_as_child
Hydra::Runner.new(:io => pipe, :verbose => @verbose) Hydra::Runner.new(:io => pipe, :verbose => @verbose, :runner_listeners => @runner_event_listeners, :runner_log_file => @runner_log_file, :options => @options)
end end
pipe.identify_as_parent pipe.identify_as_parent
@runners << { :pid => child, :io => pipe, :idle => false } @runners << { :pid => child, :io => pipe, :idle => false }
@ -99,7 +123,7 @@ module Hydra #:nodoc:
begin begin
message = @io.gets message = @io.gets
if message and !message.class.to_s.index("Master").nil? if message and !message.class.to_s.index("Master").nil?
trace "Received Message from Master" trace "Received Message from Master"
trace "\t#{message.inspect}" trace "\t#{message.inspect}"
message.handle(self) message.handle(self)
else else
@ -108,7 +132,7 @@ module Hydra #:nodoc:
end end
rescue IOError => ex rescue IOError => ex
trace "Worker lost Master" trace "Worker lost Master"
Thread.exit shutdown
end end
end end
end end

View File

@ -0,0 +1 @@
<div class="feature"><h2><span class="val">Feature: Write a file</span></h2><p class="narrative"></p><div class='scenario'><h3 id="scenario_1"><span class="keyword">Scenario:</span> <span class="val">Write to hydra_test.txt</span></h3><ol><li id='_Users_john_Projects_hydra_test_fixtures_features_write_alternate_file_feature_4' class='step passed'><div class="step_name"><span class="keyword">Given </span><span class="step val">an alternate target file</span></div><div class="step_file"><span>test/fixtures/features/step_definitions.rb:5</span></div></li><li id='_Users_john_Projects_hydra_test_fixtures_features_write_alternate_file_feature_5' class='step passed'><div class="step_name"><span class="keyword">When </span><span class="step val">I write &quot;<span class="param">HYDRA</span>&quot; to the file</span></div><div class="step_file"><span>test/fixtures/features/step_definitions.rb:9</span></div></li><li id='_Users_john_Projects_hydra_test_fixtures_features_write_alternate_file_feature_6' class='step passed'><div class="step_name"><span class="keyword">Then </span><span class="step val">&quot;<span class="param">HYDRA</span>&quot; should be written in the file</span></div><div class="step_file"><span>test/fixtures/features/step_definitions.rb:16</span></div></li></ol></div></div>

View File

@ -0,0 +1 @@
<div class="feature"><h2><span class="val">Feature: Write a file</span></h2><p class="narrative"></p><div class='scenario'><h3 id="scenario_1"><span class="keyword">Scenario:</span> <span class="val">Write to hydra_test.txt</span></h3><ol><li id='_Users_john_Projects_hydra_test_fixtures_features_write_file_feature_4' class='step passed'><div class="step_name"><span class="keyword">Given </span><span class="step val">a target file</span></div><div class="step_file"><span>test/fixtures/features/step_definitions.rb:1</span></div></li><li id='_Users_john_Projects_hydra_test_fixtures_features_write_file_feature_5' class='step passed'><div class="step_name"><span class="keyword">When </span><span class="step val">I write &quot;<span class="param">HYDRA</span>&quot; to the file</span></div><div class="step_file"><span>test/fixtures/features/step_definitions.rb:9</span></div></li><li id='_Users_john_Projects_hydra_test_fixtures_features_write_file_feature_6' class='step passed'><div class="step_name"><span class="keyword">Then </span><span class="step val">&quot;<span class="param">HYDRA</span>&quot; should be written in the file</span></div><div class="step_file"><span>test/fixtures/features/step_definitions.rb:16</span></div></li></ol></div></div>

10
test/fixtures/conflicting.rb vendored Normal file
View File

@ -0,0 +1,10 @@
require File.join(File.dirname(__FILE__), '..', 'test_helper')
# this test is around to make sure that we handle all the errors
# that can occur when 'require'ing a test file.
class SyncTest < Object
def test_it_again
assert true
end
end

View File

@ -1,9 +1,9 @@
Given /^a target file$/ do Given /^a target file$/ do
@target_file = File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt')) @target_file = File.expand_path(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'))
end end
Given /^an alternate target file$/ do Given /^an alternate target file$/ do
@target_file = File.expand_path(File.join(Dir.tmpdir, 'alternate_hydra_test.txt')) @target_file = File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'))
end end
When /^I write "([^\"]*)" to the file$/ do |text| When /^I write "([^\"]*)" to the file$/ do |text|

2
test/fixtures/hydra_worker_init.rb vendored Normal file
View File

@ -0,0 +1,2 @@
require '../test/fixtures/runner_listeners.rb'
require '../test/fixtures/master_listeners.rb'

4
test/fixtures/js_file.js vendored Normal file
View File

@ -0,0 +1,4 @@
"use strict";
var thisvar;
var thatvar

4
test/fixtures/json_data.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"var1": "something",
"var2": "trailing comma",
}

View File

@ -0,0 +1,9 @@
#!/usr/bin/env ruby
10000.times do
$stdout.write "A non-hydra message...\n"
$stdout.flush
end
$stdout.write "{:class=>Hydra::Messages::TestMessage, :text=>\"My message\"}\n"
$stdout.flush

10
test/fixtures/master_listeners.rb vendored Normal file
View File

@ -0,0 +1,10 @@
module HydraExtension
module Listener
class WorkerBeganFlag < Hydra::Listener::Abstract
# Fired after runner processes have been started
def worker_begin(worker)
FileUtils.touch File.expand_path(File.join(Dir.consistent_tmpdir, 'worker_began_flag'))
end
end
end
end

23
test/fixtures/runner_listeners.rb vendored Normal file
View File

@ -0,0 +1,23 @@
module HydraExtension
module RunnerListener
class RunnerBeginTest < Hydra::RunnerListener::Abstract
# Fired by the runner just before requesting the first file
def runner_begin( runner )
FileUtils.touch File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'))
end
end
class RunnerEndTest < Hydra::RunnerListener::Abstract
# Fired by the runner just before requesting the first file
def runner_begin( runner )
FileUtils.touch File.expand_path(File.join(Dir.consistent_tmpdir, 'runner_began_flag')) #used to know when the runner is ready
end
# Fired by the runner just after stoping
def runner_end( runner )
# NOTE: do not use trace here
#runner.trace "Ending runner"
FileUtils.touch File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'))
end
end
end
end

View File

@ -1,3 +1,5 @@
require 'rubygems'
gem 'test-unit'
require 'test/unit' require 'test/unit'
class SyncTest < Test::Unit::TestCase class SyncTest < Test::Unit::TestCase

6
test/fixtures/task_test_config.yml vendored Normal file
View File

@ -0,0 +1,6 @@
---
workers:
- type: ssh
connect: localhost
directory: /tmp
runners: 1

View File

@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), '..', 'test_helper')
class WriteFileTest < Test::Unit::TestCase class WriteFileTest < Test::Unit::TestCase
def test_write_a_file def test_write_a_file
File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'a') do |f| File.open(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'), 'a') do |f|
f.write "HYDRA" f.write "HYDRA"
end end
end end

View File

@ -1,8 +1,8 @@
require 'tmpdir' require 'rspec'
require 'spec' require 'hydra/tmpdir'
context "file writing" do describe "file writing" do
it "writes to a file" do it "writes to a file" do
File.open(File.join(Dir.tmpdir, 'alternate_hydra_test.txt'), 'a') do |f| File.open(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'), 'a') do |f|
f.write "HYDRA" f.write "HYDRA"
end end
end end

View File

@ -1,8 +1,8 @@
require 'tmpdir' require 'rspec'
require 'spec' require 'hydra/tmpdir'
context "file writing" do describe "file writing" do
it "writes to a file" do it "writes to a file" do
File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'a') do |f| File.open(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'), 'a') do |f|
f.write "HYDRA" f.write "HYDRA"
end end
end end

View File

@ -0,0 +1,12 @@
require 'tmpdir'
require 'rspec'
describe "file writing" do
it "writes to a file" do
File.open(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'), 'a') do |f|
f.write "HYDRA"
end
end
it 'could do so much more' # pending spec
end

View File

@ -1,4 +1,6 @@
require File.join(File.dirname(__FILE__), 'test_helper') require 'test_helper'
require 'fixtures/runner_listeners'
require 'fixtures/master_listeners'
class MasterTest < Test::Unit::TestCase class MasterTest < Test::Unit::TestCase
context "with a file to test and a destination to verify" do context "with a file to test and a destination to verify" do
@ -20,11 +22,53 @@ class MasterTest < Test::Unit::TestCase
assert_equal "HYDRA", File.read(target_file) assert_equal "HYDRA", File.read(target_file)
end end
# this test simulates what happens when we have 2 tests with the same
# class name but with different parent classes. This can happen when
# we have a functional and an integration test class with the same name.
#
# ...but I can't even get this test to work in the expected way (jb)
should_eventually "run even with a test that will not require" do
class FileOutputListener < Hydra::Listener::Abstract
attr_accessor :output
def initialize(&block)
self.output = {}
end
def file_end(file, output)
self.output[file] = output
end
end
listener = FileOutputListener.new
sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
Hydra::Master.new(
# we want the actual test to run last to make sure the runner can still run tests
:files => [sync_test, conflicting_test_file, test_file],
:autosort => false,
:listeners => [listener]
)
assert_match /superclass mismatch for class SyncTest/, listener.output[conflicting_test_file]
assert_match conflicting_test_file, listener.output[conflicting_test_file]
assert File.exists?(target_file)
assert_equal "HYDRA", File.read(target_file)
end
should "run a spec with pending examples" do
progress_bar = Hydra::Listener::ProgressBar.new(StringIO.new)
Hydra::Master.new(
:files => [rspec_file_with_pending],
:listeners => [progress_bar]
)
assert File.exists?(target_file)
assert_equal "HYDRA", File.read(target_file)
assert_equal false, progress_bar.instance_variable_get('@errors')
end
should "generate a report" do should "generate a report" do
Hydra::Master.new(:files => [test_file]) Hydra::Master.new(:files => [test_file])
assert File.exists?(target_file) assert File.exists?(target_file)
assert_equal "HYDRA", File.read(target_file) assert_equal "HYDRA", File.read(target_file)
report_file = File.join(Dir.tmpdir, 'hydra_heuristics.yml') report_file = File.join(Dir.consistent_tmpdir, 'hydra_heuristics.yml')
assert File.exists?(report_file) assert File.exists?(report_file)
assert report = YAML.load_file(report_file) assert report = YAML.load_file(report_file)
assert_not_nil report[test_file] assert_not_nil report[test_file]
@ -75,8 +119,8 @@ class MasterTest < Test::Unit::TestCase
:workers => [{ :workers => [{
:type => :ssh, :type => :ssh,
:connect => 'localhost', :connect => 'localhost',
:directory => File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')), :directory => remote_dir_path,
:runners => 1 :runners => 1
}] }]
) )
assert File.exists?(target_file) assert File.exists?(target_file)
@ -93,8 +137,8 @@ class MasterTest < Test::Unit::TestCase
end end
should "synchronize a test file over ssh with rsync" do should "synchronize a test file over ssh with rsync" do
local = File.join(Dir.tmpdir, 'hydra', 'local') local = File.join(Dir.consistent_tmpdir, 'hydra', 'local')
remote = File.join(Dir.tmpdir, 'hydra', 'remote') remote = File.join(Dir.consistent_tmpdir, 'hydra', 'remote')
sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb') sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
[local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f} [local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
@ -123,6 +167,7 @@ class MasterTest < Test::Unit::TestCase
:type => :ssh, :type => :ssh,
:connect => 'localhost', :connect => 'localhost',
:directory => remote, :directory => remote,
:verbose => true,
:runners => 1 :runners => 1
}], }],
:sync => { :sync => {
@ -138,4 +183,225 @@ class MasterTest < Test::Unit::TestCase
assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted" assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
end end
end end
context "with a runner_end event" do
setup do
# avoid having other tests interfering with us
sleep(0.2)
FileUtils.rm_f(target_file)
FileUtils.rm_f(alternate_target_file)
@runner_began_flag = File.expand_path(File.join(Dir.consistent_tmpdir, 'runner_began_flag')) #used to know when the worker is ready
FileUtils.rm_f(@runner_began_flag)
@runner_listener = 'HydraExtension::RunnerListener::RunnerEndTest.new' # runner_end method that creates alternate_target_file
@master_listener = HydraExtension::Listener::WorkerBeganFlag.new #used to know when the runner is up
end
teardown do
FileUtils.rm_f(target_file)
FileUtils.rm_f(alternate_target_file)
end
context "running a local worker" do
should "run runner_end on successful termination" do
@pid = Process.fork do
Hydra::Master.new(
:files => [test_file] * 6,
:autosort => false,
:listeners => [@master_listener],
:runner_listeners => [@runner_listener],
:verbose => false
)
end
Process.waitpid @pid
assert_file_exists alternate_target_file
end
should "run runner_end after interruption signal" do
add_infinite_worker_begin_to @master_listener
capture_stderr do # redirect stderr
@pid = Process.fork do
Hydra::Master.new(
:files => [test_file],
:autosort => false,
:listeners => [@master_listener],
:runner_listeners => [@runner_listener],
:verbose => false
)
end
end
wait_for_runner_to_begin
Process.kill 'SIGINT', @pid
Process.waitpid @pid
assert_file_exists alternate_target_file
end
end
context "running a remote worker" do
setup do
copy_worker_init_file # this method has a protection to avoid erasing an existing worker_init_file
end
teardown do
FileUtils.rm_f(@remote_init_file) unless @protect_init_file
end
should "run runner_end on successful termination" do
capture_stderr do # redirect stderr
@pid = Process.fork do
Hydra::Master.new(
:files => [test_file],
:autosort => false,
:listeners => [@master_listener],
:runner_listeners => [@runner_listener],
:workers => [{
:type => :ssh,
:connect => 'localhost -o ControlMaster=no',
:directory => remote_dir_path,
:runners => 1
}],
:verbose => false
)
end
end
Process.waitpid @pid
assert_file_exists target_file
end
should "not die horribly when the host cannot be reached" do
capture_stderr do # redirect stderr
@pid = Process.fork do
Hydra::Master.new(
:files => [test_file],
:autosort => false,
:listeners => [@master_listener],
:runner_listeners => [@runner_listener],
:workers => [{
:type => :ssh,
:connect => 'sdlsdkjfhadsfjsd',
:directory => remote_dir_path,
:runners => 1
}],
:verbose => false
)
end
end
Process.waitpid @pid
end
end
end
context "redirecting runner's output and errors" do
setup do
# avoid having other tests interfering with us
sleep(0.2)
FileUtils.rm_f(target_file)
FileUtils.rm_f(runner_log_file)
FileUtils.rm_f("#{remote_dir_path}/#{runner_log_file}")
end
teardown do
FileUtils.rm_f(target_file)
FileUtils.rm_f(runner_log_file)
FileUtils.rm_f("#{remote_dir_path}/#{runner_log_file}")
end
should "create a runner log file when usign local worker and passing a log file name" do
@pid = Process.fork do
Hydra::Master.new(
:files => [test_file],
:runner_log_file => runner_log_file,
:verbose => false
)
end
Process.waitpid @pid
assert_file_exists target_file # ensure the test was successfully ran
assert_file_exists runner_log_file
end
should "create a runner log file when usign remote worker and passing a log file name" do
@pid = Process.fork do
Hydra::Master.new(
:files => [test_file],
:workers => [{
:type => :ssh,
:connect => 'localhost',
:directory => remote_dir_path,
:runners => 1
}],
:verbose => false,
:runner_log_file => runner_log_file
)
end
Process.waitpid @pid
assert_file_exists target_file # ensure the test was successfully ran
assert_file_exists "#{remote_dir_path}/#{runner_log_file}"
end
should "create the default runner log file when passing an incorrect log file path" do
default_log_file = "#{remote_dir_path}/#{Hydra::Runner::DEFAULT_LOG_FILE}" # hydra-runner.log"
FileUtils.rm_f(default_log_file)
@pid = Process.fork do
Hydra::Master.new(
:files => [test_file],
:workers => [{
:type => :ssh,
:connect => 'localhost',
:directory => remote_dir_path,
:runners => 1
}],
:verbose => false,
:runner_log_file => "invalid-dir/#{runner_log_file}"
)
end
Process.waitpid @pid
assert_file_exists target_file # ensure the test was successfully ran
assert_file_exists default_log_file #default log file
assert !File.exists?( "#{remote_dir_path}/#{runner_log_file}" )
FileUtils.rm_f(default_log_file)
end
end
private
def runner_log_file
"my-hydra-runner.log"
end
def add_infinite_worker_begin_to master_listener
class << master_listener
def worker_begin( worker )
super
sleep 1 while true #ensure the process doesn't finish before killing it
end
end
end
# this requires that a worker_begin listener creates a file named worker_began_flag in tmp directory
def wait_for_runner_to_begin
assert_file_exists @runner_began_flag
end
# with a protection to avoid erasing something important in lib
def copy_worker_init_file
@remote_init_file = "#{remote_dir_path}/#{File.basename( hydra_worker_init_file )}"
if File.exists?( @remote_init_file )
$stderr.puts "\nWARNING!!!: #{@remote_init_file} exits and this test needs to create a new file here. Make sure there is nothing inportant in that file and remove it before running this test\n\n"
@protect_init_file = true
exit
end
# copy the hydra_worker_init to the correct location
FileUtils.cp(hydra_worker_init_file, remote_dir_path)
end
end end

View File

@ -1,4 +1,4 @@
require File.join(File.dirname(__FILE__), 'test_helper') require 'test_helper'
class MessageTest < Test::Unit::TestCase class MessageTest < Test::Unit::TestCase
class MyMessage < Hydra::Message class MyMessage < Hydra::Message

View File

@ -1,4 +1,4 @@
require File.join(File.dirname(__FILE__), 'test_helper') require 'test_helper'
class PipeTest < Test::Unit::TestCase class PipeTest < Test::Unit::TestCase
context "a pipe" do context "a pipe" do

View File

@ -1,4 +1,5 @@
require File.join(File.dirname(__FILE__), 'test_helper') require 'test_helper'
require 'fixtures/runner_listeners'
class RunnerTest < Test::Unit::TestCase class RunnerTest < Test::Unit::TestCase
context "with a file to test and a destination to verify" do context "with a file to test and a destination to verify" do
@ -37,8 +38,20 @@ class RunnerTest < Test::Unit::TestCase
Process.wait(child) Process.wait(child)
end end
should "run a js lint file and find errors" do
runner = Hydra::Runner.new(:options => {}, :io => File.new('/dev/null', 'w'))
results = runner.run_file(javascript_file)
assert results =~ /Missing semicolon/, results
end
should "run a json data file and find errors" do
runner = Hydra::Runner.new(:options => {}, :io => File.new('/dev/null', 'w'))
results = runner.run_file(json_file)
assert results =~ /trailing comma/, results
end
should "run two rspec tests" do should "run two rspec tests" do
runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w')) runner = Hydra::Runner.new(:options => {}, :io => File.new('/dev/null', 'w'))
runner.run_file(rspec_file) runner.run_file(rspec_file)
assert File.exists?(target_file) assert File.exists?(target_file)
assert_equal "HYDRA", File.read(target_file) assert_equal "HYDRA", File.read(target_file)
@ -48,58 +61,110 @@ class RunnerTest < Test::Unit::TestCase
runner.run_file(alternate_rspec_file) runner.run_file(alternate_rspec_file)
assert File.exists?(alternate_target_file) assert File.exists?(alternate_target_file)
assert_equal "HYDRA", File.read(alternate_target_file) assert_equal "HYDRA", File.read(alternate_target_file)
assert !File.exists?(target_file) assert !File.exists?(target_file), "Tests are double running!"
end
should "run rspec tests with pending examples" do
runner = Hydra::Runner.new(:options => {}, :io => File.new('/dev/null', 'w'))
assert File.exists?(rspec_file_with_pending)
runner.run_file(rspec_file_with_pending)
assert File.exists?(target_file)
assert_equal "HYDRA", File.read(target_file)
FileUtils.rm_f(target_file)
end end
should "run two cucumber tests" do should "run two cucumber tests" do
# because of all the crap cucumber pulls in # because of all the crap cucumber pulls in
# we run this in a fork to not contaminate # we run this in a fork to not contaminate
# the main test environment # the main test environment
pid = Process.fork do capture_stderr do # redirect stderr
puts "THE FOLLOWING WARNINGS CAN BE IGNORED" pid = Process.fork do
puts "It is caused by Cucumber loading all rb files near its features" runner = Hydra::Runner.new(:options => {}, :io => File.new('/dev/null', 'w'))
runner.run_file(cucumber_feature_file)
assert File.exists?(target_file)
assert_equal "HYDRA", File.read(target_file)
runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w')) FileUtils.rm_f(target_file)
runner.run_file(cucumber_feature_file)
assert File.exists?(target_file) runner.run_file(alternate_cucumber_feature_file)
assert_equal "HYDRA", File.read(target_file) assert File.exists?(alternate_target_file)
assert_equal "HYDRA", File.read(alternate_target_file)
FileUtils.rm_f(target_file) assert !File.exists?(target_file)
end
runner.run_file(alternate_cucumber_feature_file) Process.wait pid
assert File.exists?(alternate_target_file)
assert_equal "HYDRA", File.read(alternate_target_file)
assert !File.exists?(target_file)
puts "END IGNORABLE OUTPUT"
end end
Process.wait pid
end end
should "be able to run a runner over ssh" do should "be able to run a runner over ssh" do
ssh = Hydra::SSH.new( ssh = Hydra::SSH.new(
'localhost', 'localhost -o ControlMaster=no',
File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')), File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
"ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Runner.new(:io => Hydra::Stdio.new, :verbose => true);\"" %{ruby -rrubygems -e "require \\"bundler/setup\\"; require \\"hydra\\"; Hydra::Runner.new(:options => {}, :io => Hydra::Stdio.new, :verbose => true);"}
) )
assert ssh.gets.is_a?(Hydra::Messages::Runner::RequestFile) assert ssh.gets.is_a?(Hydra::Messages::Runner::RequestFile)
ssh.write(Hydra::Messages::Worker::RunFile.new(:file => test_file)) ssh.write(Hydra::Messages::Worker::RunFile.new(:file => test_file))
# grab its response. This makes us wait for it to finish # grab its response. This makes us wait for it to finish
echo = ssh.gets # get the ssh echo echo = ssh.gets # get the ssh echo
response = ssh.gets response = ssh.gets
assert_equal Hydra::Messages::Runner::Results, response.class assert_equal Hydra::Messages::Runner::Results, response.class
# tell it to shut down # tell it to shut down
ssh.write(Hydra::Messages::Worker::Shutdown.new) ssh.write(Hydra::Messages::Worker::Shutdown.new)
ssh.close ssh.close
# ensure it ran # ensure it ran
assert File.exists?(target_file) assert File.exists?(target_file)
assert_equal "HYDRA", File.read(target_file) assert_equal "HYDRA", File.read(target_file)
end end
context "using runner events" do
context "on successful termination" do
setup do
@pipe = Hydra::Pipe.new
@parent = Process.fork do
request_a_file_and_verify_completion(@pipe, test_file)
end
end
should "fire runner_begin event" do
run_the_runner(@pipe, [HydraExtension::RunnerListener::RunnerBeginTest.new] )
Process.wait(@parent)
# ensure runner_begin was fired
assert_file_exists alternate_target_file
end
should "fire runner_end event" do
run_the_runner(@pipe, [HydraExtension::RunnerListener::RunnerEndTest.new] )
Process.wait(@parent)
assert_file_exists alternate_target_file
end
end
should "fire runner_end event after losing communication with worker" do
pipe = Hydra::Pipe.new
parent = Process.fork do
pipe.identify_as_parent
# grab its response.
response = pipe.gets
pipe.close #this will be detected by the runner and it should call runner_end
end
run_the_runner(pipe, [HydraExtension::RunnerListener::RunnerEndTest.new] )
Process.wait(parent)
# ensure runner_end was fired
assert File.exists?( alternate_target_file )
end
end
end end
module RunnerTestHelper module RunnerTestHelper
@ -112,7 +177,6 @@ class RunnerTest < Test::Unit::TestCase
# grab its response. This makes us wait for it to finish # grab its response. This makes us wait for it to finish
response = pipe.gets response = pipe.gets
puts response.output
# tell it to shut down # tell it to shut down
pipe.write(Hydra::Messages::Worker::Shutdown.new) pipe.write(Hydra::Messages::Worker::Shutdown.new)
@ -122,9 +186,9 @@ class RunnerTest < Test::Unit::TestCase
assert_equal "HYDRA", File.read(target_file) assert_equal "HYDRA", File.read(target_file)
end end
def run_the_runner(pipe) def run_the_runner(pipe, listeners = [])
pipe.identify_as_child pipe.identify_as_child
Hydra::Runner.new(:io => pipe) Hydra::Runner.new( :io => pipe, :options => {}, :runner_listeners => listeners )
end end
end end
include RunnerTestHelper include RunnerTestHelper

View File

@ -1,9 +1,9 @@
require File.join(File.dirname(__FILE__), 'test_helper') require 'test_helper'
class SSHTest < Test::Unit::TestCase class SSHTest < Test::Unit::TestCase
should "be able to execute a command over ssh" do should "be able to execute a command over ssh" do
ssh = Hydra::SSH.new( ssh = Hydra::SSH.new(
'localhost', # connect to this machine 'localhost -o ControlMaster=no', # connect to this machine
File.expand_path(File.join(File.dirname(__FILE__))), # move to the test directory File.expand_path(File.join(File.dirname(__FILE__))), # move to the test directory
"ruby fixtures/hello_world.rb" "ruby fixtures/hello_world.rb"
) )
@ -11,4 +11,16 @@ class SSHTest < Test::Unit::TestCase
assert_equal "Hello World", response.text assert_equal "Hello World", response.text
ssh.close ssh.close
end end
should "be able to handle a large number of non-Hydra console output" do
ssh = Hydra::SSH.new(
'localhost -o ControlMaster=no', # connect to this machine
File.expand_path(File.join(File.dirname(__FILE__))), # move to the test directory
"ruby fixtures/many_outputs_to_console.rb"
)
response = ssh.gets
assert_equal "My message", response.text
ssh.close
end
end end

116
test/sync_test.rb Normal file
View File

@ -0,0 +1,116 @@
require 'test_helper'
class SyncTest < Test::Unit::TestCase
context "with a file to test and a destination to verify" do
setup do
# avoid having other tests interfering with us
sleep(0.2)
#FileUtils.rm_f(target_file)
end
teardown do
#FileUtils.rm_f(target_file)
end
should "synchronize a test file over ssh with rsync" do
local = File.join(Dir.consistent_tmpdir, 'hydra', 'local')
remote = File.join(Dir.consistent_tmpdir, 'hydra', 'remote')
sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
[local, remote].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
# setup the folders:
# local:
# - test_a
# - test_c
# remote:
# - test_b
#
# add test_c to exludes
FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
FileUtils.cp(sync_test, File.join(local, 'test_c.rb'))
FileUtils.cp(sync_test, File.join(remote, 'test_b.rb'))
# ensure a is not on remote
assert !File.exists?(File.join(remote, 'test_a.rb')), "A should not be on remote"
# ensure c is not on remote
assert !File.exists?(File.join(remote, 'test_c.rb')), "C should not be on remote"
# ensure b is on remote
assert File.exists?(File.join(remote, 'test_b.rb')), "B should be on remote"
$stderr.puts local
$stderr.puts remote
Hydra::Sync.new(
{
:type => :ssh,
:connect => 'localhost',
:directory => remote,
:runners => 1
},
{
:directory => local,
:exclude => ['test_c.rb']
}
)
# ensure a is copied
assert File.exists?(File.join(remote, 'test_a.rb')), "A was not copied"
# ensure c is not copied
assert !File.exists?(File.join(remote, 'test_c.rb')), "C was copied, should be excluded"
# ensure b is deleted
assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
end
should "synchronize a test file over ssh with rsync to multiple workers" do
local = File.join(Dir.consistent_tmpdir, 'hydra', 'local')
remote_a = File.join(Dir.consistent_tmpdir, 'hydra', 'remote_a')
remote_b = File.join(Dir.consistent_tmpdir, 'hydra', 'remote_b')
sync_test = File.join(File.dirname(__FILE__), 'fixtures', 'sync_test.rb')
[local, remote_a, remote_b].each{|f| FileUtils.rm_rf f; FileUtils.mkdir_p f}
# setup the folders:
# local:
# - test_a
# remote_a:
# - test_b
# remote_b:
# - test_c
#
# add test_c to exludes
FileUtils.cp(sync_test, File.join(local, 'test_a.rb'))
FileUtils.cp(sync_test, File.join(remote_a, 'test_b.rb'))
FileUtils.cp(sync_test, File.join(remote_b, 'test_c.rb'))
# ensure a is not on remotes
assert !File.exists?(File.join(remote_a, 'test_a.rb')), "A should not be on remote_a"
assert !File.exists?(File.join(remote_b, 'test_a.rb')), "A should not be on remote_b"
# ensure b is on remote_a
assert File.exists?(File.join(remote_a, 'test_b.rb')), "B should be on remote_a"
# ensure c is on remote_b
assert File.exists?(File.join(remote_b, 'test_c.rb')), "C should be on remote_b"
Hydra::Sync.sync_many(
:workers => [{
:type => :ssh,
:connect => 'localhost',
:directory => remote_a,
:runners => 1
},
{
:type => :ssh,
:connect => 'localhost',
:directory => remote_b,
:runners => 1
}],
:sync => {
:directory => local
}
)
# ensure a is copied to both remotes
assert File.exists?(File.join(remote_a, 'test_a.rb')), "A was not copied to remote_a"
assert File.exists?(File.join(remote_b, 'test_a.rb')), "A was not copied to remote_b"
# ensure b and c are deleted from remotes
assert !File.exists?(File.join(remote_a, 'test_b.rb')), "B was not deleted from remote_a"
assert !File.exists?(File.join(remote_b, 'test_c.rb')), "C was not deleted from remote_b"
end
end
end

21
test/task_test.rb Normal file
View File

@ -0,0 +1,21 @@
require 'test_helper'
require 'hydra/tasks'
require 'rake'
class TaskTest < Test::Unit::TestCase
context "a task" do
should "execute the command in a remote machine" do
File.delete( "/tmp/new_file" ) if File.exists? "/tmp/new_file"
Hydra::RemoteTask.new('cat:text_file', 'touch new_file') do |t|
t.config = "test/fixtures/task_test_config.yml"
end
Rake.application['hydra:remote:cat:text_file'].invoke
assert( File.exists? "/tmp/new_file" )
end
end
end

View File

@ -1,22 +1,24 @@
require 'rubygems' require 'rubygems'
gem 'test-unit'
require 'test/unit' require 'test/unit'
require 'shoulda' require 'shoulda'
require 'tmpdir' require 'tmpdir'
require "stringio"
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.dirname(__FILE__))
require 'hydra' require 'hydra'
# Since Hydra turns off testing, we have to turn it back on # Since Hydra turns off testing, we have to turn it back on
Test::Unit.run = false #Test::Unit.run = false
class Test::Unit::TestCase class Test::Unit::TestCase
def target_file def target_file
File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt')) File.expand_path(File.join(Dir.consistent_tmpdir, 'hydra_test.txt'))
end end
def alternate_target_file def alternate_target_file
File.expand_path(File.join(Dir.tmpdir, 'alternate_hydra_test.txt')) File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt'))
end end
def test_file def test_file
@ -31,6 +33,10 @@ class Test::Unit::TestCase
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_alternate_spec.rb')) File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_alternate_spec.rb'))
end end
def rspec_file_with_pending
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'write_file_with_pending_spec.rb'))
end
def cucumber_feature_file def cucumber_feature_file
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_file.feature')) File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_file.feature'))
end end
@ -38,6 +44,50 @@ class Test::Unit::TestCase
def alternate_cucumber_feature_file def alternate_cucumber_feature_file
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_alternate_file.feature')) File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_alternate_file.feature'))
end end
def javascript_file
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'js_file.js'))
end
def json_file
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'json_data.json'))
end
def conflicting_test_file
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'conflicting.rb'))
end
def remote_dir_path
File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
end
def hydra_worker_init_file
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'hydra_worker_init.rb'))
end
def capture_stderr
# The output stream must be an IO-like object. In this case we capture it in
# an in-memory IO object so we can return the string value. You can assign any
# IO object here.
previous_stderr, $stderr = $stderr, StringIO.new
yield
$stderr.string
ensure
# Restore the previous value of stderr (typically equal to STDERR).
$stderr = previous_stderr
end
#this method allow us to wait for a file for a maximum number of time, so the
#test can pass in slower machines. This helps to speed up the tests
def assert_file_exists file, time_to_wait = 2
time_begin = Time.now
until Time.now - time_begin >= time_to_wait or File.exists?( file ) do
sleep 0.01
end
assert File.exists?( file )
end
end end
module Hydra #:nodoc: module Hydra #:nodoc:

View File

@ -1,4 +1,4 @@
require File.join(File.dirname(__FILE__), 'test_helper') require 'test_helper'
class WorkerTest < Test::Unit::TestCase class WorkerTest < Test::Unit::TestCase
context "with a file to test and a destination to verify" do context "with a file to test and a destination to verify" do
@ -36,13 +36,15 @@ class WorkerTest < Test::Unit::TestCase
module WorkerTestHelper module WorkerTestHelper
def run_the_worker(pipe, num_runners) def run_the_worker(pipe, num_runners)
pipe.identify_as_child pipe.identify_as_child
Hydra::Worker.new({:io => pipe, :runners => num_runners}) Hydra::Worker.new({:io => pipe, :runners => num_runners, :options => {}})
end end
def request_a_file_and_verify_completion(pipe, num_runners) def request_a_file_and_verify_completion(pipe, num_runners)
pipe.identify_as_parent pipe.identify_as_parent
pipe.gets # grab the WorkerBegin
num_runners.times do num_runners.times do
assert pipe.gets.is_a?(Hydra::Messages::Worker::RequestFile) response = pipe.gets # grab the RequestFile
assert response.is_a?(Hydra::Messages::Worker::RequestFile), "Expected RequestFile but got #{response.class.to_s}"
end end
pipe.write(Hydra::Messages::Master::RunFile.new(:file => test_file)) pipe.write(Hydra::Messages::Master::RunFile.new(:file => test_file))