Compare commits

..

1 Commits

Author SHA1 Message Date
Nick Gauthier 825368a7ec first spike on ruby 1.9 w/ minitest 2010-04-16 10:12:15 -04:00
57 changed files with 384 additions and 7354 deletions

3
.gitignore vendored
View File

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

View File

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

View File

@ -1,48 +0,0 @@
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-2010 Nick Gauthier Copyright (c) 2009 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,10 +11,8 @@ 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", "~> 2.6.0" gem.add_development_dependency "rspec", "= 1.3.0"
gem.add_development_dependency "rspec-core", "= 2.6.4" gem.add_development_dependency "cucumber", "= 0.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
@ -23,7 +21,7 @@ end
require 'rake/testtask' require 'rake/testtask'
Rake::TestTask.new(:test) do |test| Rake::TestTask.new(:test) do |test|
test.libs << 'test' test.libs << 'lib' << 'test'
test.pattern = 'test/**/*_test.rb' test.pattern = 'test/**/*_test.rb'
test.verbose = true test.verbose = true
end end
@ -41,6 +39,8 @@ 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.23.3 0.16.2

View File

@ -1,191 +1,117 @@
# 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 'rake gemspec' # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
# -*- 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.23.3" s.version = "0.16.2"
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 = [%q{Nick Gauthier}] s.authors = ["Nick Gauthier"]
s.date = %q{2011-08-31} s.date = %q{2010-04-11}
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",
"Gemfile", ".gitignore",
"Gemfile.lock", "LICENSE",
"LICENSE", "README.rdoc",
"README.rdoc", "Rakefile",
"Rakefile", "TODO",
"TODO", "VERSION",
"VERSION", "caliper.yml",
"caliper.yml", "hydra-icon-64x64.png",
"hydra-icon-64x64.png", "hydra.gemspec",
"hydra.gemspec", "hydra_gray.png",
"hydra_gray.png", "lib/hydra.rb",
"lib/hydra.rb", "lib/hydra/cucumber/formatter.rb",
"lib/hydra/config.rb", "lib/hydra/hash.rb",
"lib/hydra/cucumber/formatter.rb", "lib/hydra/listener/abstract.rb",
"lib/hydra/cucumber/partial_html.rb", "lib/hydra/listener/minimal_output.rb",
"lib/hydra/hash.rb", "lib/hydra/listener/notifier.rb",
"lib/hydra/js/lint.js", "lib/hydra/listener/progress_bar.rb",
"lib/hydra/listener/abstract.rb", "lib/hydra/listener/report_generator.rb",
"lib/hydra/listener/cucumber.css", "lib/hydra/master.rb",
"lib/hydra/listener/cucumber_html_report.rb", "lib/hydra/message.rb",
"lib/hydra/listener/jquery-min.js", "lib/hydra/message/master_messages.rb",
"lib/hydra/listener/minimal_output.rb", "lib/hydra/message/runner_messages.rb",
"lib/hydra/listener/notifier.rb", "lib/hydra/message/worker_messages.rb",
"lib/hydra/listener/progress_bar.rb", "lib/hydra/messaging_io.rb",
"lib/hydra/listener/report_generator.rb", "lib/hydra/pipe.rb",
"lib/hydra/master.rb", "lib/hydra/runner.rb",
"lib/hydra/message.rb", "lib/hydra/safe_fork.rb",
"lib/hydra/message/master_messages.rb", "lib/hydra/spec/autorun_override.rb",
"lib/hydra/message/runner_messages.rb", "lib/hydra/spec/hydra_formatter.rb",
"lib/hydra/message/worker_messages.rb", "lib/hydra/ssh.rb",
"lib/hydra/messaging_io.rb", "lib/hydra/stdio.rb",
"lib/hydra/pipe.rb", "lib/hydra/tasks.rb",
"lib/hydra/runner.rb", "lib/hydra/trace.rb",
"lib/hydra/runner_listener/abstract.rb", "lib/hydra/worker.rb",
"lib/hydra/safe_fork.rb", "test/fixtures/assert_true.rb",
"lib/hydra/spec/autorun_override.rb", "test/fixtures/config.yml",
"lib/hydra/spec/hydra_formatter.rb", "test/fixtures/features/step_definitions.rb",
"lib/hydra/ssh.rb", "test/fixtures/features/write_alternate_file.feature",
"lib/hydra/stdio.rb", "test/fixtures/features/write_file.feature",
"lib/hydra/sync.rb", "test/fixtures/hello_world.rb",
"lib/hydra/tasks.rb", "test/fixtures/slow.rb",
"lib/hydra/tmpdir.rb", "test/fixtures/sync_test.rb",
"lib/hydra/trace.rb", "test/fixtures/write_file.rb",
"lib/hydra/worker.rb", "test/fixtures/write_file_alternate_spec.rb",
"test/fixtures/assert_true.rb", "test/fixtures/write_file_spec.rb",
"test/fixtures/config.yml", "test/master_test.rb",
"test/fixtures/conflicting.rb", "test/message_test.rb",
"test/fixtures/features/step_definitions.rb", "test/pipe_test.rb",
"test/fixtures/features/write_alternate_file.feature", "test/runner_test.rb",
"test/fixtures/features/write_file.feature", "test/ssh_test.rb",
"test/fixtures/hello_world.rb", "test/test_helper.rb",
"test/fixtures/hydra_worker_init.rb", "test/worker_test.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.require_paths = [%q{lib}] s.rdoc_options = ["--charset=UTF-8"]
s.rubygems_version = %q{1.8.9} s.require_paths = ["lib"]
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::VERSION) >= Gem::Version.new('1.2.0') then if Gem::Version.new(Gem::RubyGemsVersion) >= 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<cucumber>, ["= 0.9.2"]) s.add_development_dependency(%q<rspec>, ["= 1.3.0"])
s.add_development_dependency(%q<therubyracer>, ["= 0.7.4"]) s.add_development_dependency(%q<cucumber>, ["= 0.6.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<cucumber>, ["= 0.9.2"]) s.add_dependency(%q<rspec>, ["= 1.3.0"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"]) s.add_dependency(%q<cucumber>, ["= 0.6.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<cucumber>, ["= 0.9.2"]) s.add_dependency(%q<rspec>, ["= 1.3.0"])
s.add_dependency(%q<therubyracer>, ["= 0.7.4"]) s.add_dependency(%q<cucumber>, ["= 0.6.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,10 +7,9 @@ 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'

View File

@ -1,21 +0,0 @@
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,6 +19,7 @@ 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

@ -1,24 +0,0 @@
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

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,23 +10,14 @@ 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, after the workers shutdown # Fired when testing finishes
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

@ -1,279 +0,0 @@
/* 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

@ -1,148 +0,0 @@
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

View File

@ -1,154 +0,0 @@
/*!
* 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[0;31m" : "\033[0;32m" @output.write @errors ? "\033[1;31m" : "\033[1;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,7 +18,6 @@ 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
@ -29,5 +28,3 @@ module Hydra #:nodoc:
end end
end end
end end

View File

@ -1,22 +1,15 @@
require 'hydra/hash' require 'hydra/hash'
require 'hydra/config'
require 'open3' require 'open3'
require 'hydra/tmpdir' require '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:
@ -38,14 +31,11 @@ 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
config_yml = Hydra::Config.load(config_file) opts.merge!(YAML.load_file(config_file).stringify_keys!)
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 } )
@ -54,36 +44,18 @@ 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})"
@ -96,11 +68,8 @@ module Hydra #:nodoc:
end end
# Message handling # Message handling
def worker_begin(worker)
@event_listeners.each {|l| l.worker_begin(worker) } # Send a file down to a 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
@ -114,29 +83,13 @@ 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)
if message.output =~ /ActiveRecord::StatementInvalid(.*)[Dd]eadlock/ or @incomplete_files.delete_at(@incomplete_files.index(message.file))
message.output =~ /PGError: ERROR(.*)[Dd]eadlock/ or trace "#{@incomplete_files.size} Files Remaining"
message.output =~ /Mysql::Error: SAVEPOINT(.*)does not exist: ROLLBACK/ or @event_listeners.each{|l| l.file_end(message.file, message.output) }
message.output =~ /Mysql::Error: Deadlock found/ if @incomplete_files.empty?
trace "Deadlock detected running [#{message.file}]. Will retry at the end" shutdown_all_workers
@files.push(message.file)
send_file(worker)
else else
@incomplete_files.delete_at(@incomplete_files.index(message.file)) send_file(worker)
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
@ -144,7 +97,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|
@ -163,39 +116,57 @@ 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, :runner_listeners => @string_runner_event_listeners, :runner_log_file => @runner_log_file, :options => @options ) Hydra::Worker.new(:io => pipe, :runners => runners, :verbose => @verbose)
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)
sync = Sync.new(worker, @sync, @verbose) runners = worker.fetch('runners') { raise "You must specify the number of runners" }
if sync.result == 0 connect = worker.fetch('connect') { raise "You must specify an SSH connection target" }
runners = worker.fetch('runners') { raise "You must specify the number of runners" } ssh_opts = worker.fetch('ssh_opts') { "" }
command = worker.fetch('command') { directory = worker.fetch('directory') { raise "You must specify a remote directory" }
%{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 => {} );"} command = worker.fetch('command') {
} "ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose});\""
}
trace "Booting SSH worker" if @sync
trace command @sync.stringify_keys!
ssh = Hydra::SSH.new("#{sync.ssh_opts} #{sync.connect}", sync.remote_dir, command, worker['timeout']) trace "Synchronizing with #{connect}\n\t#{@sync.inspect}"
return { :io => ssh, :idle => false, :type => :ssh, :connect => sync.connect } local_dir = @sync.fetch('directory') {
else raise "You must specify a synchronization directory"
false }
exclude_paths = @sync.fetch('exclude') { [] }
exclude_opts = exclude_paths.inject(''){|memo, path| memo += "--exclude=#{path} "}
rsync_command = [
'rsync',
'-avz',
'--delete',
exclude_opts,
File.expand_path(local_dir)+'/',
"-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
@ -210,31 +181,25 @@ 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 if worker @workers << worker
end end
if worker while true
dead_count = 0 begin
while true message = worker[:io].gets
begin trace "got message: #{message}"
message = worker[:io].gets # if it exists and its for me.
trace "got message: #{message}" # SSH gives us back echoes, so we need to ignore our own messages
# if it exists and its for me. if message and !message.class.to_s.index("Worker").nil?
# SSH gives us back echoes, so we need to ignore our own messages message.handle(self, worker)
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
@ -254,7 +219,7 @@ module Hydra #:nodoc:
end end
def heuristic_file def heuristic_file
@heuristic_file ||= File.join(Dir.consistent_tmpdir, 'hydra_heuristics.yml') @heuristic_file ||= File.join(Dir.tmpdir, 'hydra_heuristics.yml')
end end
end end
end end

View File

@ -8,12 +8,6 @@ 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,21 +8,14 @@ module Hydra #:nodoc:
# IO.gets # IO.gets
# => Hydra::Message # or subclass # => Hydra::Message # or subclass
def gets def gets
while true raise IOError unless @reader
begin message = @reader.gets
raise IOError unless @reader return nil unless message
message = nil return Message.build(eval(message.chomp))
if result = Kernel.select([@reader], [], [], @timeout) rescue SyntaxError, NameError
message = @reader.gets # uncomment to help catch remote errors by seeing all traffic
end #$stderr.write "Not a message: [#{message.inspect}]\n"
return Message.build(:class => Hydra::Messages::Master::Shutdown) if result == nil return gets
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,10 +31,9 @@ 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(timeout = nil) def initialize
@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,3 +1,16 @@
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.
# #
@ -9,26 +22,14 @@ 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 = {})
redirect_output( opts.fetch( :runner_log_file ) { DEFAULT_LOG_FILE } ) @io = opts.fetch(:io) { raise "No IO Object" }
reg_trap_sighup @verbose = opts.fetch(:verbose) { false }
@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 $stdout.sync = true
runner_begin
trace 'Booted. Sending Request for file' trace 'Booted. Sending Request for file'
@io.write RequestFile.new @io.write RequestFile.new
begin begin
process_messages process_messages
@ -38,32 +39,15 @@ 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$/i if file =~ /_spec.rb$/
output = run_rspec_file(file) output = run_rspec_file(file)
elsif file =~ /.feature$/i elsif file =~ /.feature$/
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
@ -76,17 +60,11 @@ module Hydra #:nodoc:
# Stop running # Stop running
def stop def stop
runner_end if @runner_began @running = false
@runner_began = @running = false
end end
def runner_end def puke(klass, name, exception)
trace "Ending runner #{self.inspect}" puts "puke! #{klass}, #{name}, #{exception}"
@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
@ -104,46 +82,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"
stop @running = false
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| klass.suite.run(@result){|status, name| ;}} klasses.each{|klass|
#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 << format_ex_in_file(file, ex) output << ex.to_s
end end
return output.join("\n") return output.join("\n")
@ -153,111 +131,74 @@ module Hydra #:nodoc:
def run_rspec_file(file) def run_rspec_file(file)
# pull in rspec # pull in rspec
begin begin
require 'rspec' require 'spec'
require 'hydra/spec/hydra_formatter'
# Ensure we override rspec's at_exit # Ensure we override rspec's at_exit
RSpec::Core::Runner.disable_autorun! require 'hydra/spec/autorun_override'
rescue LoadError => ex rescue LoadError => ex
return ex.to_s return ex.to_s
end end
@hydra_output ||= StringIO.new hydra_output = StringIO.new
@hydra_output.rewind Spec::Runner.options.instance_variable_set(:@formatters, [
@hydra_output.truncate(0) Spec::Runner::Formatter::HydraFormatter.new(
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","") =~ /^\.*$/
config = [ file ] return output
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
options = @options if @options.is_a?(Array) unless @step_mother
options = @options.split(' ') if @options.is_a?(String) require 'cucumber'
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'
require 'hydra/cucumber/partial_html' @step_mother = Cucumber::StepMother.new
@cuke_configuration = Cucumber::Cli::Configuration.new(dev_null, dev_null)
@cuke_configuration.parse!(['features']+files)
Cucumber.logger.level = Logger::INFO @step_mother.options = @cuke_configuration.options
@step_mother.log = @cuke_configuration.log
cuke = Cucumber::Cli::Main.new(args, dev_null, dev_null) @step_mother.load_code_files(@cuke_configuration.support_to_load)
cuke.configuration.formats << ['Cucumber::Formatter::Hydra', hydra_response] @step_mother.after_configuration(@cuke_configuration)
@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
Process.wait fork_id cuke_formatter = Cucumber::Formatter::Hydra.new(
@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
@ -281,7 +222,11 @@ module Hydra #:nodoc:
nil nil
end end
end end
return klasses.select{|k| k.respond_to? 'suite'} puts "Pre klasses: #{klasses.inspect}"
puts klasses
return klasses.select{|k| k.respond_to? 'test_suites'}
end end
# Yanked a method from Cucumber # Yanked a method from Cucumber
@ -295,16 +240,5 @@ 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

@ -1,23 +0,0 @@
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
connection = ActiveRecord::Base.remove_connection if defined?(ActiveRecord) 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((connection || {}).merge({:allow_concurrency => true})) if defined?(ActiveRecord) ActiveRecord::Base.establish_connection 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((connection || {}).merge({:allow_concurrency => true})) if defined?(ActiveRecord) ActiveRecord::Base.establish_connection 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,3 +1,12 @@
if defined?(RSpec) if defined?(Spec)
RSpec::Core::Runner.disable_autorun! module Spec
module Runner
class << self
# stop the auto-run at_exit
def run
return 0
end
end
end
end
end end

View File

@ -1,22 +1,11 @@
require 'rspec/core/formatters/progress_formatter' require 'spec/runner/formatter/progress_bar_formatter'
module RSpec module Spec
module Core module Runner
module Formatters module Formatter
class HydraFormatter < ProgressFormatter class HydraFormatter < ProgressBarFormatter
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,9 +25,16 @@ 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, timeout = nil) def initialize(connection_options, directory, command)
@timeout = timeout @writer, @reader, @error = popen3("ssh -tt #{connection_options}")
@writer, @reader, @error = popen3(%{ssh -tt #{connection_options} 'mkdir -p #{directory} && cd #{directory} && #{command}'}) @writer.write("cd #{directory}\n")
@writer.write(command+"\n")
end
# Close the SSH connection
def close
@writer.write "exit\n"
super
end end
end end
end end

View File

@ -1,107 +0,0 @@
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,15 +1,10 @@
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]
@ -36,20 +31,6 @@ 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
@ -59,7 +40,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:
# #
@ -78,7 +59,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
@ -87,30 +68,20 @@ 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
if defined?(RSpec) require 'hydra/spec/autorun_override'
RSpec::Core::Runner.disable_autorun!
end
unless @serial @config = find_config_file
@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)
@ -126,132 +97,8 @@ 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
if Object.const_defined?('Rails') && Rails.env == 'development' Hydra::Master.new(@opts)
$stderr.puts %{WARNING: Rails Environment is "development". Make sure to set it properly (ex: "RAILS_ENV=test rake hydra")} #exit(0) #bypass test on_exit output
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
@ -265,9 +112,8 @@ 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, command=nil) def initialize(name)
@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
@ -281,42 +127,34 @@ 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 = Hydra::Config.load(@config) config = YAML.load_file(@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|
@listeners << Thread.new do $stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n"
begin ssh_opts = worker.fetch('ssh_opts') { '' }
@results[worker] = if run_command(worker, @command) writer, reader, error = popen3("ssh -tt #{ssh_opts} #{worker['connect']} ")
"==== #{@name} passed on #{worker['connect']} ====\n" writer.write("cd #{worker['directory']}\n")
else writer.write "echo BEGIN HYDRA\n"
"==== #{@name} failed on #{worker['connect']} ====\nPlease see above for more details.\n" writer.write("RAILS_ENV=test rake #{@name}\n")
end writer.write "echo END HYDRA\n"
rescue writer.write("exit\n")
@results[worker] = "==== #{@name} failed for #{worker['connect']} ====\n#{$!.inspect}\n#{$!.backtrace.join("\n")}" writer.close
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.
@ -326,7 +164,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
@ -348,7 +186,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

View File

@ -1,11 +0,0 @@
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,8 +9,6 @@ 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
@ -19,37 +17,16 @@ 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
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
# 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)
@ -91,10 +68,9 @@ 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, :runner_listeners => @runner_event_listeners, :runner_log_file => @runner_log_file, :options => @options) Hydra::Runner.new(:io => pipe, :verbose => @verbose)
end end
pipe.identify_as_parent pipe.identify_as_parent
@runners << { :pid => child, :io => pipe, :idle => false } @runners << { :pid => child, :io => pipe, :idle => false }
@ -123,7 +99,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
@ -132,7 +108,7 @@ module Hydra #:nodoc:
end end
rescue IOError => ex rescue IOError => ex
trace "Worker lost Master" trace "Worker lost Master"
shutdown Thread.exit
end end
end end
end end

View File

@ -1 +0,0 @@
<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

@ -1 +0,0 @@
<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>

View File

@ -1,10 +0,0 @@
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.consistent_tmpdir, 'hydra_test.txt')) @target_file = File.expand_path(File.join(Dir.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.consistent_tmpdir, 'alternate_hydra_test.txt')) @target_file = File.expand_path(File.join(Dir.tmpdir, 'alternate_hydra_test.txt'))
end end
When /^I write "([^\"]*)" to the file$/ do |text| When /^I write "([^\"]*)" to the file$/ do |text|

View File

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

View File

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

View File

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

View File

@ -1,9 +0,0 @@
#!/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

View File

@ -1,10 +0,0 @@
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

View File

@ -1,23 +0,0 @@
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,5 +1,3 @@
require 'rubygems'
gem 'test-unit'
require 'test/unit' require 'test/unit'
class SyncTest < Test::Unit::TestCase class SyncTest < Test::Unit::TestCase

View File

@ -1,6 +0,0 @@
---
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.consistent_tmpdir, 'hydra_test.txt'), 'a') do |f| File.open(File.join(Dir.tmpdir, 'hydra_test.txt'), 'a') do |f|
f.write "HYDRA" f.write "HYDRA"
end end
end end

View File

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

View File

@ -1,12 +0,0 @@
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,6 +1,4 @@
require 'test_helper' require File.join(File.dirname(__FILE__), '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
@ -22,53 +20,11 @@ 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.consistent_tmpdir, 'hydra_heuristics.yml') report_file = File.join(Dir.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]
@ -119,8 +75,8 @@ class MasterTest < Test::Unit::TestCase
:workers => [{ :workers => [{
:type => :ssh, :type => :ssh,
:connect => 'localhost', :connect => 'localhost',
:directory => remote_dir_path, :directory => File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
:runners => 1 :runners => 1
}] }]
) )
assert File.exists?(target_file) assert File.exists?(target_file)
@ -137,8 +93,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.consistent_tmpdir, 'hydra', 'local') local = File.join(Dir.tmpdir, 'hydra', 'local')
remote = File.join(Dir.consistent_tmpdir, 'hydra', 'remote') remote = File.join(Dir.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}
@ -167,7 +123,6 @@ 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 => {
@ -183,225 +138,4 @@ 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 'test_helper' require File.join(File.dirname(__FILE__), '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 'test_helper' require File.join(File.dirname(__FILE__), 'test_helper')
class PipeTest < Test::Unit::TestCase class PipeTest < Test::Unit::TestCase
context "a pipe" do context "a pipe" do

View File

@ -1,5 +1,4 @@
require 'test_helper' require File.join(File.dirname(__FILE__), '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
@ -38,20 +37,8 @@ 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(:options => {}, :io => File.new('/dev/null', 'w')) runner = Hydra::Runner.new(: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)
@ -61,110 +48,58 @@ 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), "Tests are double running!" assert !File.exists?(target_file)
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
capture_stderr do # redirect stderr pid = Process.fork do
pid = Process.fork do puts "THE FOLLOWING WARNINGS CAN BE IGNORED"
runner = Hydra::Runner.new(:options => {}, :io => File.new('/dev/null', 'w')) puts "It is caused by Cucumber loading all rb files near its features"
runner.run_file(cucumber_feature_file)
assert File.exists?(target_file)
assert_equal "HYDRA", File.read(target_file)
FileUtils.rm_f(target_file) runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
runner.run_file(cucumber_feature_file)
runner.run_file(alternate_cucumber_feature_file) assert File.exists?(target_file)
assert File.exists?(alternate_target_file) assert_equal "HYDRA", File.read(target_file)
assert_equal "HYDRA", File.read(alternate_target_file)
assert !File.exists?(target_file) FileUtils.rm_f(target_file)
end
Process.wait pid runner.run_file(alternate_cucumber_feature_file)
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 -o ControlMaster=no', 'localhost',
File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')), File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
%{ruby -rrubygems -e "require \\"bundler/setup\\"; require \\"hydra\\"; Hydra::Runner.new(:options => {}, :io => Hydra::Stdio.new, :verbose => true);"} "ruby -e \"require 'rubygems'; require 'hydra'; Hydra::Runner.new(: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
@ -177,6 +112,7 @@ 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)
@ -186,9 +122,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, listeners = []) def run_the_runner(pipe)
pipe.identify_as_child pipe.identify_as_child
Hydra::Runner.new( :io => pipe, :options => {}, :runner_listeners => listeners ) Hydra::Runner.new(:io => pipe)
end end
end end
include RunnerTestHelper include RunnerTestHelper

View File

@ -1,9 +1,9 @@
require 'test_helper' require File.join(File.dirname(__FILE__), '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 -o ControlMaster=no', # connect to this machine 'localhost', # 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,16 +11,4 @@ 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

View File

@ -1,116 +0,0 @@
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

View File

@ -1,21 +0,0 @@
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,24 +1,22 @@
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.consistent_tmpdir, 'hydra_test.txt')) File.expand_path(File.join(Dir.tmpdir, 'hydra_test.txt'))
end end
def alternate_target_file def alternate_target_file
File.expand_path(File.join(Dir.consistent_tmpdir, 'alternate_hydra_test.txt')) File.expand_path(File.join(Dir.tmpdir, 'alternate_hydra_test.txt'))
end end
def test_file def test_file
@ -33,10 +31,6 @@ 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
@ -44,50 +38,6 @@ 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 'test_helper' require File.join(File.dirname(__FILE__), '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,15 +36,13 @@ 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, :options => {}}) Hydra::Worker.new({:io => pipe, :runners => num_runners})
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
response = pipe.gets # grab the RequestFile assert pipe.gets.is_a?(Hydra::Messages::Worker::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))