Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Nick Gauthier | 825368a7ec |
|
@ -17,8 +17,5 @@ tmtags
|
|||
coverage
|
||||
rdoc
|
||||
pkg
|
||||
tags
|
||||
.rvmrc
|
||||
hydra-runner.log
|
||||
|
||||
## PROJECT::SPECIFIC
|
||||
|
|
5
Gemfile
5
Gemfile
|
@ -1,5 +0,0 @@
|
|||
source :rubygems
|
||||
|
||||
gemspec
|
||||
gem 'rake', '0.8.7'
|
||||
gem 'test-unit', :require => 'test/unit'
|
48
Gemfile.lock
48
Gemfile.lock
|
@ -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)
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||
a copy of this software and associated documentation files (the
|
||||
|
|
10
Rakefile
10
Rakefile
|
@ -11,10 +11,8 @@ begin
|
|||
gem.homepage = "http://github.com/ngauthier/hydra"
|
||||
gem.authors = ["Nick Gauthier"]
|
||||
gem.add_development_dependency "shoulda", "= 2.10.3"
|
||||
gem.add_development_dependency "rspec", "~> 2.6.0"
|
||||
gem.add_development_dependency "rspec-core", "= 2.6.4"
|
||||
gem.add_development_dependency "cucumber", "= 0.9.2"
|
||||
gem.add_development_dependency "therubyracer", "= 0.7.4"
|
||||
gem.add_development_dependency "rspec", "= 1.3.0"
|
||||
gem.add_development_dependency "cucumber", "= 0.6.4"
|
||||
end
|
||||
Jeweler::GemcutterTasks.new
|
||||
rescue LoadError
|
||||
|
@ -23,7 +21,7 @@ end
|
|||
|
||||
require 'rake/testtask'
|
||||
Rake::TestTask.new(:test) do |test|
|
||||
test.libs << 'test'
|
||||
test.libs << 'lib' << 'test'
|
||||
test.pattern = 'test/**/*_test.rb'
|
||||
test.verbose = true
|
||||
end
|
||||
|
@ -41,6 +39,8 @@ rescue LoadError
|
|||
end
|
||||
end
|
||||
|
||||
task :test => :check_dependencies
|
||||
|
||||
task :default => :test
|
||||
|
||||
require 'rake/rdoctask'
|
||||
|
|
246
hydra.gemspec
246
hydra.gemspec
|
@ -1,191 +1,117 @@
|
|||
# Generated by jeweler
|
||||
# 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 -*-
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
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.authors = [%q{Nick Gauthier}]
|
||||
s.date = %q{2011-08-31}
|
||||
s.authors = ["Nick Gauthier"]
|
||||
s.date = %q{2010-04-11}
|
||||
s.description = %q{Spread your tests over multiple machines to test your code faster.}
|
||||
s.email = %q{nick@smartlogicsolutions.com}
|
||||
s.extra_rdoc_files = [
|
||||
"LICENSE",
|
||||
"README.rdoc",
|
||||
"TODO"
|
||||
"README.rdoc",
|
||||
"TODO"
|
||||
]
|
||||
s.files = [
|
||||
".document",
|
||||
"Gemfile",
|
||||
"Gemfile.lock",
|
||||
"LICENSE",
|
||||
"README.rdoc",
|
||||
"Rakefile",
|
||||
"TODO",
|
||||
"VERSION",
|
||||
"caliper.yml",
|
||||
"hydra-icon-64x64.png",
|
||||
"hydra.gemspec",
|
||||
"hydra_gray.png",
|
||||
"lib/hydra.rb",
|
||||
"lib/hydra/config.rb",
|
||||
"lib/hydra/cucumber/formatter.rb",
|
||||
"lib/hydra/cucumber/partial_html.rb",
|
||||
"lib/hydra/hash.rb",
|
||||
"lib/hydra/js/lint.js",
|
||||
"lib/hydra/listener/abstract.rb",
|
||||
"lib/hydra/listener/cucumber.css",
|
||||
"lib/hydra/listener/cucumber_html_report.rb",
|
||||
"lib/hydra/listener/jquery-min.js",
|
||||
"lib/hydra/listener/minimal_output.rb",
|
||||
"lib/hydra/listener/notifier.rb",
|
||||
"lib/hydra/listener/progress_bar.rb",
|
||||
"lib/hydra/listener/report_generator.rb",
|
||||
"lib/hydra/master.rb",
|
||||
"lib/hydra/message.rb",
|
||||
"lib/hydra/message/master_messages.rb",
|
||||
"lib/hydra/message/runner_messages.rb",
|
||||
"lib/hydra/message/worker_messages.rb",
|
||||
"lib/hydra/messaging_io.rb",
|
||||
"lib/hydra/pipe.rb",
|
||||
"lib/hydra/runner.rb",
|
||||
"lib/hydra/runner_listener/abstract.rb",
|
||||
"lib/hydra/safe_fork.rb",
|
||||
"lib/hydra/spec/autorun_override.rb",
|
||||
"lib/hydra/spec/hydra_formatter.rb",
|
||||
"lib/hydra/ssh.rb",
|
||||
"lib/hydra/stdio.rb",
|
||||
"lib/hydra/sync.rb",
|
||||
"lib/hydra/tasks.rb",
|
||||
"lib/hydra/tmpdir.rb",
|
||||
"lib/hydra/trace.rb",
|
||||
"lib/hydra/worker.rb",
|
||||
"test/fixtures/assert_true.rb",
|
||||
"test/fixtures/config.yml",
|
||||
"test/fixtures/conflicting.rb",
|
||||
"test/fixtures/features/step_definitions.rb",
|
||||
"test/fixtures/features/write_alternate_file.feature",
|
||||
"test/fixtures/features/write_file.feature",
|
||||
"test/fixtures/hello_world.rb",
|
||||
"test/fixtures/hydra_worker_init.rb",
|
||||
"test/fixtures/js_file.js",
|
||||
"test/fixtures/json_data.json",
|
||||
"test/fixtures/many_outputs_to_console.rb",
|
||||
"test/fixtures/master_listeners.rb",
|
||||
"test/fixtures/runner_listeners.rb",
|
||||
"test/fixtures/slow.rb",
|
||||
"test/fixtures/sync_test.rb",
|
||||
"test/fixtures/task_test_config.yml",
|
||||
"test/fixtures/write_file.rb",
|
||||
"test/fixtures/write_file_alternate_spec.rb",
|
||||
"test/fixtures/write_file_spec.rb",
|
||||
"test/fixtures/write_file_with_pending_spec.rb",
|
||||
"test/master_test.rb",
|
||||
"test/message_test.rb",
|
||||
"test/pipe_test.rb",
|
||||
"test/runner_test.rb",
|
||||
"test/ssh_test.rb",
|
||||
"test/sync_test.rb",
|
||||
"test/task_test.rb",
|
||||
"test/test_helper.rb",
|
||||
"test/worker_test.rb"
|
||||
".gitignore",
|
||||
"LICENSE",
|
||||
"README.rdoc",
|
||||
"Rakefile",
|
||||
"TODO",
|
||||
"VERSION",
|
||||
"caliper.yml",
|
||||
"hydra-icon-64x64.png",
|
||||
"hydra.gemspec",
|
||||
"hydra_gray.png",
|
||||
"lib/hydra.rb",
|
||||
"lib/hydra/cucumber/formatter.rb",
|
||||
"lib/hydra/hash.rb",
|
||||
"lib/hydra/listener/abstract.rb",
|
||||
"lib/hydra/listener/minimal_output.rb",
|
||||
"lib/hydra/listener/notifier.rb",
|
||||
"lib/hydra/listener/progress_bar.rb",
|
||||
"lib/hydra/listener/report_generator.rb",
|
||||
"lib/hydra/master.rb",
|
||||
"lib/hydra/message.rb",
|
||||
"lib/hydra/message/master_messages.rb",
|
||||
"lib/hydra/message/runner_messages.rb",
|
||||
"lib/hydra/message/worker_messages.rb",
|
||||
"lib/hydra/messaging_io.rb",
|
||||
"lib/hydra/pipe.rb",
|
||||
"lib/hydra/runner.rb",
|
||||
"lib/hydra/safe_fork.rb",
|
||||
"lib/hydra/spec/autorun_override.rb",
|
||||
"lib/hydra/spec/hydra_formatter.rb",
|
||||
"lib/hydra/ssh.rb",
|
||||
"lib/hydra/stdio.rb",
|
||||
"lib/hydra/tasks.rb",
|
||||
"lib/hydra/trace.rb",
|
||||
"lib/hydra/worker.rb",
|
||||
"test/fixtures/assert_true.rb",
|
||||
"test/fixtures/config.yml",
|
||||
"test/fixtures/features/step_definitions.rb",
|
||||
"test/fixtures/features/write_alternate_file.feature",
|
||||
"test/fixtures/features/write_file.feature",
|
||||
"test/fixtures/hello_world.rb",
|
||||
"test/fixtures/slow.rb",
|
||||
"test/fixtures/sync_test.rb",
|
||||
"test/fixtures/write_file.rb",
|
||||
"test/fixtures/write_file_alternate_spec.rb",
|
||||
"test/fixtures/write_file_spec.rb",
|
||||
"test/master_test.rb",
|
||||
"test/message_test.rb",
|
||||
"test/pipe_test.rb",
|
||||
"test/runner_test.rb",
|
||||
"test/ssh_test.rb",
|
||||
"test/test_helper.rb",
|
||||
"test/worker_test.rb"
|
||||
]
|
||||
s.homepage = %q{http://github.com/ngauthier/hydra}
|
||||
s.require_paths = [%q{lib}]
|
||||
s.rubygems_version = %q{1.8.9}
|
||||
s.rdoc_options = ["--charset=UTF-8"]
|
||||
s.require_paths = ["lib"]
|
||||
s.rubygems_version = %q{1.3.6}
|
||||
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
|
||||
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
||||
s.specification_version = 3
|
||||
|
||||
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
||||
s.add_runtime_dependency(%q<rake>)
|
||||
s.add_runtime_dependency(%q<test-unit>, [">= 0"])
|
||||
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
||||
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<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"])
|
||||
s.add_development_dependency(%q<rspec>, ["= 1.3.0"])
|
||||
s.add_development_dependency(%q<cucumber>, ["= 0.6.4"])
|
||||
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<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
||||
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
||||
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
||||
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<rspec>, ["= 1.3.0"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.6.4"])
|
||||
end
|
||||
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<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
||||
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
||||
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
||||
s.add_dependency(%q<rspec-core>, ["= 2.6.4"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.9.2"])
|
||||
s.add_dependency(%q<therubyracer>, ["= 0.7.4"])
|
||||
s.add_dependency(%q<rspec>, ["= 1.3.0"])
|
||||
s.add_dependency(%q<cucumber>, ["= 0.6.4"])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,10 +7,9 @@ require 'hydra/safe_fork'
|
|||
require 'hydra/runner'
|
||||
require 'hydra/worker'
|
||||
require 'hydra/master'
|
||||
require 'hydra/sync'
|
||||
require 'hydra/listener/abstract'
|
||||
require 'hydra/listener/minimal_output'
|
||||
require 'hydra/listener/report_generator'
|
||||
require 'hydra/listener/notifier'
|
||||
require 'hydra/listener/progress_bar'
|
||||
require 'hydra/runner_listener/abstract'
|
||||
|
||||
|
|
|
@ -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
|
|
@ -19,6 +19,7 @@ module Cucumber #:nodoc:
|
|||
print_steps(:failed)
|
||||
print_snippets(@options)
|
||||
print_passing_wip(@options)
|
||||
print_tag_limit_warnings(features)
|
||||
end
|
||||
|
||||
# Removed all progress output
|
||||
|
|
|
@ -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
|
5150
lib/hydra/js/lint.js
5150
lib/hydra/js/lint.js
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,7 @@ module Hydra #:nodoc:
|
|||
module Listener #:nodoc:
|
||||
# Abstract listener that implements all the events
|
||||
# but does nothing.
|
||||
class Abstract
|
||||
class Abstract
|
||||
# Create a new listener.
|
||||
#
|
||||
# Output: The IO object for outputting any information.
|
||||
|
@ -10,23 +10,14 @@ module Hydra #:nodoc:
|
|||
def initialize(output = $stdout)
|
||||
@output = output
|
||||
end
|
||||
|
||||
# Fired when testing has started
|
||||
def testing_begin(files)
|
||||
end
|
||||
|
||||
# Fired when testing finishes, after the workers shutdown
|
||||
# Fired when testing finishes
|
||||
def testing_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
|
||||
def file_begin(file)
|
||||
end
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
|
@ -34,7 +34,7 @@ module Hydra #:nodoc:
|
|||
complete = ((@files_completed.to_f / @total_files.to_f) * width).to_i
|
||||
@output.write "\r" # move to beginning
|
||||
@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 '#'}
|
||||
@output.write '>'
|
||||
(width-complete).times{@output.write ' '}
|
||||
|
|
|
@ -18,7 +18,6 @@ module Hydra #:nodoc:
|
|||
def file_end(file, output)
|
||||
@report[file]['end'] = Time.now.to_f
|
||||
@report[file]['duration'] = @report[file]['end'] - @report[file]['start']
|
||||
@report[file]['all_tests_passed_last_run'] = (output == '.')
|
||||
end
|
||||
|
||||
# output the report
|
||||
|
@ -29,5 +28,3 @@ module Hydra #:nodoc:
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -1,22 +1,15 @@
|
|||
require 'hydra/hash'
|
||||
require 'hydra/config'
|
||||
require 'open3'
|
||||
require 'hydra/tmpdir'
|
||||
require 'erb'
|
||||
require 'tmpdir'
|
||||
require 'yaml'
|
||||
|
||||
module Hydra #:nodoc:
|
||||
# Hydra class responsible for delegate work down to workers.
|
||||
#
|
||||
# The Master is run once for any given testing session.
|
||||
class YmlLoadError < StandardError; end
|
||||
|
||||
class Master
|
||||
include Hydra::Messages::Master
|
||||
include Open3
|
||||
traceable('MASTER')
|
||||
attr_reader :failed_files
|
||||
|
||||
# Create a new Master
|
||||
#
|
||||
# Options:
|
||||
|
@ -38,14 +31,11 @@ module Hydra #:nodoc:
|
|||
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!)
|
||||
opts.merge!(YAML.load_file(config_file).stringify_keys!)
|
||||
end
|
||||
@files = Array(opts.fetch('files') { nil })
|
||||
raise "No files, nothing to do" if @files.empty?
|
||||
@incomplete_files = @files.dup
|
||||
@failed_files = []
|
||||
@workers = []
|
||||
@listeners = []
|
||||
@event_listeners = Array(opts.fetch('listeners') { nil } )
|
||||
|
@ -54,36 +44,18 @@ module Hydra #:nodoc:
|
|||
listener = eval(l)
|
||||
@event_listeners << listener if listener.is_a?(Hydra::Listener::Abstract)
|
||||
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 }
|
||||
@autosort = opts.fetch('autosort') { true }
|
||||
@sync = opts.fetch('sync') { nil }
|
||||
@environment = opts.fetch('environment') { 'test' } || 'test'
|
||||
@options = opts.fetch('options') { '' }
|
||||
|
||||
if @autosort
|
||||
sort_files_from_report
|
||||
sort_files_from_report
|
||||
@event_listeners << Hydra::Listener::ReportGenerator.new(File.new(heuristic_file, 'w'))
|
||||
end
|
||||
|
||||
# default is one worker that is configured to use a pipe with one runner
|
||||
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 " Files: (#{@files.inspect})"
|
||||
trace " Workers: (#{worker_cfg.inspect})"
|
||||
|
@ -96,11 +68,8 @@ module Hydra #:nodoc:
|
|||
end
|
||||
|
||||
# Message handling
|
||||
def worker_begin(worker)
|
||||
@event_listeners.each {|l| l.worker_begin(worker) }
|
||||
end
|
||||
|
||||
# Send a file down to a worker.
|
||||
|
||||
# Send a file down to a worker.
|
||||
def send_file(worker)
|
||||
f = @files.shift
|
||||
if f
|
||||
|
@ -114,29 +83,13 @@ module Hydra #:nodoc:
|
|||
|
||||
# Process the results coming back from the worker.
|
||||
def process_results(worker, message)
|
||||
if message.output =~ /ActiveRecord::StatementInvalid(.*)[Dd]eadlock/ or
|
||||
message.output =~ /PGError: ERROR(.*)[Dd]eadlock/ or
|
||||
message.output =~ /Mysql::Error: SAVEPOINT(.*)does not exist: ROLLBACK/ or
|
||||
message.output =~ /Mysql::Error: Deadlock found/
|
||||
trace "Deadlock detected running [#{message.file}]. Will retry at the end"
|
||||
@files.push(message.file)
|
||||
send_file(worker)
|
||||
@incomplete_files.delete_at(@incomplete_files.index(message.file))
|
||||
trace "#{@incomplete_files.size} Files Remaining"
|
||||
@event_listeners.each{|l| l.file_end(message.file, message.output) }
|
||||
if @incomplete_files.empty?
|
||||
shutdown_all_workers
|
||||
else
|
||||
@incomplete_files.delete_at(@incomplete_files.index(message.file))
|
||||
trace "#{@incomplete_files.size} Files Remaining"
|
||||
@event_listeners.each{|l| l.file_end(message.file, message.output) }
|
||||
unless message.output == '.'
|
||||
@failed_files << message.file
|
||||
end
|
||||
if @incomplete_files.empty?
|
||||
@workers.each do |worker|
|
||||
@event_listeners.each{|l| l.worker_end(worker) }
|
||||
end
|
||||
|
||||
shutdown_all_workers
|
||||
else
|
||||
send_file(worker)
|
||||
end
|
||||
send_file(worker)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -144,7 +97,7 @@ module Hydra #:nodoc:
|
|||
attr_reader :report_text
|
||||
|
||||
private
|
||||
|
||||
|
||||
def boot_workers(workers)
|
||||
trace "Booting #{workers.size} workers"
|
||||
workers.each do |worker|
|
||||
|
@ -163,39 +116,57 @@ module Hydra #:nodoc:
|
|||
|
||||
def boot_local_worker(worker)
|
||||
runners = worker.fetch('runners') { raise "You must specify the number of runners" }
|
||||
trace "Booting local worker"
|
||||
trace "Booting local worker"
|
||||
pipe = Hydra::Pipe.new
|
||||
child = SafeFork.fork do
|
||||
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
|
||||
|
||||
pipe.identify_as_parent
|
||||
@workers << { :pid => child, :io => pipe, :idle => false, :type => :local }
|
||||
end
|
||||
|
||||
def boot_ssh_worker(worker)
|
||||
sync = Sync.new(worker, @sync, @verbose)
|
||||
if sync.result == 0
|
||||
runners = worker.fetch('runners') { raise "You must specify the number of runners" }
|
||||
command = worker.fetch('command') {
|
||||
%{RAILS_ENV=#{@environment} ruby -rrubygems -e "require \\"bundler/setup\\"; require \\"hydra\\"; Hydra::Worker.new(:io => Hydra::Stdio.new, :runners => #{runners}, :verbose => #{@verbose}, :runner_listeners => \\"#{@string_runner_event_listeners}\\", :runner_log_file => \\"#{@runner_log_file}\\", :options => {} );"}
|
||||
}
|
||||
runners = worker.fetch('runners') { raise "You must specify the number of runners" }
|
||||
connect = worker.fetch('connect') { raise "You must specify an SSH connection target" }
|
||||
ssh_opts = worker.fetch('ssh_opts') { "" }
|
||||
directory = worker.fetch('directory') { raise "You must specify a remote directory" }
|
||||
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"
|
||||
trace command
|
||||
ssh = Hydra::SSH.new("#{sync.ssh_opts} #{sync.connect}", sync.remote_dir, command, worker['timeout'])
|
||||
return { :io => ssh, :idle => false, :type => :ssh, :connect => sync.connect }
|
||||
else
|
||||
false
|
||||
if @sync
|
||||
@sync.stringify_keys!
|
||||
trace "Synchronizing with #{connect}\n\t#{@sync.inspect}"
|
||||
local_dir = @sync.fetch('directory') {
|
||||
raise "You must specify a synchronization directory"
|
||||
}
|
||||
exclude_paths = @sync.fetch('exclude') { [] }
|
||||
exclude_opts = exclude_paths.inject(''){|memo, path| memo += "--exclude=#{path} "}
|
||||
|
||||
rsync_command = [
|
||||
'rsync',
|
||||
'-avz',
|
||||
'--delete',
|
||||
exclude_opts,
|
||||
File.expand_path(local_dir)+'/',
|
||||
"-e \"ssh #{ssh_opts}\"",
|
||||
"#{connect}:#{directory}"
|
||||
].join(" ")
|
||||
trace rsync_command
|
||||
trace `#{rsync_command}`
|
||||
end
|
||||
|
||||
trace "Booting SSH worker"
|
||||
ssh = Hydra::SSH.new("#{ssh_opts} #{connect}", directory, command)
|
||||
return { :io => ssh, :idle => false, :type => :ssh }
|
||||
end
|
||||
|
||||
def shutdown_all_workers
|
||||
trace "Shutting down all workers"
|
||||
@workers.each do |worker|
|
||||
worker[:io].write(Shutdown.new) if worker[:io]
|
||||
worker[:io].close if worker[:io]
|
||||
worker[:io].close if worker[:io]
|
||||
end
|
||||
@listeners.each{|t| t.exit}
|
||||
end
|
||||
|
@ -210,31 +181,25 @@ module Hydra #:nodoc:
|
|||
trace "Listening to #{worker.inspect}"
|
||||
if worker.fetch('type') { 'local' }.to_s == 'ssh'
|
||||
worker = boot_ssh_worker(worker)
|
||||
@workers << worker if worker
|
||||
@workers << worker
|
||||
end
|
||||
if worker
|
||||
dead_count = 0
|
||||
while true
|
||||
begin
|
||||
message = worker[:io].gets
|
||||
trace "got message: #{message}"
|
||||
# if it exists and its for me.
|
||||
# SSH gives us back echoes, so we need to ignore our own messages
|
||||
if message and !message.class.to_s.index("Worker").nil?
|
||||
message.handle(self, worker)
|
||||
else
|
||||
dead_count += 1
|
||||
raise IOError if dead_count > 100
|
||||
end
|
||||
rescue IOError
|
||||
trace "lost Worker [#{worker.inspect}]"
|
||||
Thread.exit
|
||||
while true
|
||||
begin
|
||||
message = worker[:io].gets
|
||||
trace "got message: #{message}"
|
||||
# if it exists and its for me.
|
||||
# SSH gives us back echoes, so we need to ignore our own messages
|
||||
if message and !message.class.to_s.index("Worker").nil?
|
||||
message.handle(self, worker)
|
||||
end
|
||||
rescue IOError
|
||||
trace "lost Worker [#{worker.inspect}]"
|
||||
Thread.exit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@listeners.each{|l| l.join}
|
||||
@event_listeners.each{|l| l.testing_end}
|
||||
end
|
||||
|
@ -254,7 +219,7 @@ module Hydra #:nodoc:
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -8,12 +8,6 @@ module Hydra #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
class WorkerBegin < Hydra::Message
|
||||
def handle(master, worker)
|
||||
master.worker_begin(worker)
|
||||
end
|
||||
end
|
||||
|
||||
# Message telling the Runner to run a file
|
||||
class RunFile < Hydra::Message
|
||||
# The file that should be run
|
||||
|
|
|
@ -8,21 +8,14 @@ module Hydra #:nodoc:
|
|||
# IO.gets
|
||||
# => Hydra::Message # or subclass
|
||||
def gets
|
||||
while true
|
||||
begin
|
||||
raise IOError unless @reader
|
||||
message = nil
|
||||
if result = Kernel.select([@reader], [], [], @timeout)
|
||||
message = @reader.gets
|
||||
end
|
||||
return Message.build(:class => Hydra::Messages::Master::Shutdown) if result == nil
|
||||
return nil unless message
|
||||
return Message.build(eval(message.chomp))
|
||||
rescue SyntaxError, NameError, Errno::EBADF
|
||||
# uncomment to help catch remote errors by seeing all traffic
|
||||
#$stderr.write "Not a message: [#{message.inspect}]\n"
|
||||
end
|
||||
end
|
||||
raise IOError unless @reader
|
||||
message = @reader.gets
|
||||
return nil unless message
|
||||
return Message.build(eval(message.chomp))
|
||||
rescue SyntaxError, NameError
|
||||
# uncomment to help catch remote errors by seeing all traffic
|
||||
#$stderr.write "Not a message: [#{message.inspect}]\n"
|
||||
return gets
|
||||
end
|
||||
|
||||
# Write a Message to the output IO object. It will automatically
|
||||
|
|
|
@ -31,10 +31,9 @@ module Hydra #:nodoc:
|
|||
class Pipe
|
||||
include Hydra::MessagingIO
|
||||
# Creates a new uninitialized pipe pair.
|
||||
def initialize(timeout = nil)
|
||||
def initialize
|
||||
@child_read, @parent_write = IO.pipe
|
||||
@parent_read, @child_write = IO.pipe
|
||||
@timeout = timeout
|
||||
end
|
||||
|
||||
# Identify this side of the pipe as the child.
|
||||
|
|
|
@ -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:
|
||||
# Hydra class responsible for running test files.
|
||||
#
|
||||
|
@ -9,26 +22,14 @@ module Hydra #:nodoc:
|
|||
class Runner
|
||||
include Hydra::Messages::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
|
||||
# parent) to send it messages on which files to execute.
|
||||
def initialize(opts = {})
|
||||
redirect_output( opts.fetch( :runner_log_file ) { DEFAULT_LOG_FILE } )
|
||||
reg_trap_sighup
|
||||
|
||||
@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)
|
||||
|
||||
@io = opts.fetch(:io) { raise "No IO Object" }
|
||||
@verbose = opts.fetch(:verbose) { false }
|
||||
$stdout.sync = true
|
||||
runner_begin
|
||||
|
||||
trace 'Booted. Sending Request for file'
|
||||
|
||||
@io.write RequestFile.new
|
||||
begin
|
||||
process_messages
|
||||
|
@ -38,32 +39,15 @@ module Hydra #:nodoc:
|
|||
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
|
||||
def run_file(file)
|
||||
trace "Running file: #{file}"
|
||||
|
||||
output = ""
|
||||
if file =~ /_spec.rb$/i
|
||||
if file =~ /_spec.rb$/
|
||||
output = run_rspec_file(file)
|
||||
elsif file =~ /.feature$/i
|
||||
elsif file =~ /.feature$/
|
||||
output = run_cucumber_file(file)
|
||||
elsif file =~ /.js$/i or file =~ /.json$/i
|
||||
output = run_javascript_file(file)
|
||||
else
|
||||
output = run_test_unit_file(file)
|
||||
end
|
||||
|
@ -76,17 +60,11 @@ module Hydra #:nodoc:
|
|||
|
||||
# Stop running
|
||||
def stop
|
||||
runner_end if @runner_began
|
||||
@runner_began = @running = false
|
||||
@running = false
|
||||
end
|
||||
|
||||
def runner_end
|
||||
trace "Ending runner #{self.inspect}"
|
||||
@event_listeners.each {|l| l.runner_end( self ) }
|
||||
end
|
||||
|
||||
def format_exception(ex)
|
||||
"#{ex.class.name}: #{ex.message}\n #{ex.backtrace.join("\n ")}"
|
||||
def puke(klass, name, exception)
|
||||
puts "puke! #{klass}, #{name}, #{exception}"
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -104,46 +82,46 @@ module Hydra #:nodoc:
|
|||
message.handle(self)
|
||||
else
|
||||
@io.write Ping.new
|
||||
sleep WAIT_BETWEEN_PING
|
||||
end
|
||||
rescue IOError => ex
|
||||
trace "Runner lost Worker"
|
||||
stop
|
||||
@running = false
|
||||
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
|
||||
def run_test_unit_file(file)
|
||||
begin
|
||||
gem 'test-unit'
|
||||
require 'test/unit'
|
||||
require 'test/unit/testresult'
|
||||
Test::Unit.run = true
|
||||
require file
|
||||
rescue LoadError => ex
|
||||
trace "#{file} does not exist [#{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
|
||||
output = []
|
||||
@result = Test::Unit::TestResult.new
|
||||
@result.add_listener(Test::Unit::TestResult::FAULT) do |value|
|
||||
output << value
|
||||
end
|
||||
# @result = Test::Unit::TestResult.new
|
||||
# @result.add_listener(Test::Unit::TestResult::FAULT) do |value|
|
||||
# output << value
|
||||
# end
|
||||
|
||||
|
||||
klasses = Runner.find_classes_in_file(file)
|
||||
puts "Klasses: #{klasses.inspect}"
|
||||
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
|
||||
output << format_ex_in_file(file, ex)
|
||||
output << ex.to_s
|
||||
end
|
||||
|
||||
return output.join("\n")
|
||||
|
@ -153,111 +131,74 @@ module Hydra #:nodoc:
|
|||
def run_rspec_file(file)
|
||||
# pull in rspec
|
||||
begin
|
||||
require 'rspec'
|
||||
require 'spec'
|
||||
require 'hydra/spec/hydra_formatter'
|
||||
# Ensure we override rspec's at_exit
|
||||
RSpec::Core::Runner.disable_autorun!
|
||||
require 'hydra/spec/autorun_override'
|
||||
rescue LoadError => ex
|
||||
return ex.to_s
|
||||
end
|
||||
@hydra_output ||= StringIO.new
|
||||
@hydra_output.rewind
|
||||
@hydra_output.truncate(0)
|
||||
hydra_output = StringIO.new
|
||||
Spec::Runner.options.instance_variable_set(:@formatters, [
|
||||
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 ]
|
||||
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 : ""
|
||||
return output
|
||||
end
|
||||
|
||||
# run all the scenarios in a cucumber feature file
|
||||
def run_cucumber_file(file)
|
||||
|
||||
files = [file]
|
||||
dev_null = StringIO.new
|
||||
hydra_response = StringIO.new
|
||||
|
||||
options = @options if @options.is_a?(Array)
|
||||
options = @options.split(' ') if @options.is_a?(String)
|
||||
|
||||
fork_id = fork do
|
||||
files = [file]
|
||||
dev_null = StringIO.new
|
||||
|
||||
args = [file, options].flatten.compact
|
||||
hydra_response.puts args.inspect
|
||||
|
||||
results_directory = "#{Dir.pwd}/results/features"
|
||||
FileUtils.mkdir_p results_directory
|
||||
|
||||
require 'cucumber/cli/main'
|
||||
unless @step_mother
|
||||
require 'cucumber'
|
||||
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
|
||||
|
||||
cuke = Cucumber::Cli::Main.new(args, dev_null, dev_null)
|
||||
cuke.configuration.formats << ['Cucumber::Formatter::Hydra', hydra_response]
|
||||
|
||||
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?
|
||||
@step_mother.options = @cuke_configuration.options
|
||||
@step_mother.log = @cuke_configuration.log
|
||||
@step_mother.load_code_files(@cuke_configuration.support_to_load)
|
||||
@step_mother.after_configuration(@cuke_configuration)
|
||||
@step_mother.load_code_files(@cuke_configuration.step_defs_to_load)
|
||||
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.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
|
||||
return hydra_response.read
|
||||
end
|
||||
|
||||
# find all the test unit classes in a given file, so we can run their suites
|
||||
|
@ -281,7 +222,11 @@ module Hydra #:nodoc:
|
|||
nil
|
||||
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
|
||||
|
||||
# Yanked a method from Cucumber
|
||||
|
@ -295,16 +240,5 @@ module Hydra #:nodoc:
|
|||
end
|
||||
end.compact
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -2,13 +2,13 @@ class SafeFork
|
|||
def self.fork
|
||||
begin
|
||||
# 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
|
||||
child = Process.fork do
|
||||
begin
|
||||
# create a new connection and perform the action
|
||||
begin
|
||||
ActiveRecord::Base.establish_connection((connection || {}).merge({:allow_concurrency => true})) if defined?(ActiveRecord)
|
||||
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
|
||||
rescue ActiveRecord::AdapterNotSpecified
|
||||
# AR was defined but we didn't have a connection
|
||||
end
|
||||
|
@ -21,7 +21,7 @@ class SafeFork
|
|||
ensure
|
||||
# make sure we re-establish the connection before returning to the main instance
|
||||
begin
|
||||
ActiveRecord::Base.establish_connection((connection || {}).merge({:allow_concurrency => true})) if defined?(ActiveRecord)
|
||||
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
|
||||
rescue ActiveRecord::AdapterNotSpecified
|
||||
# AR was defined but we didn't have a connection
|
||||
end
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
if defined?(RSpec)
|
||||
RSpec::Core::Runner.disable_autorun!
|
||||
if defined?(Spec)
|
||||
module Spec
|
||||
module Runner
|
||||
class << self
|
||||
# stop the auto-run at_exit
|
||||
def run
|
||||
return 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,22 +1,11 @@
|
|||
require 'rspec/core/formatters/progress_formatter'
|
||||
module RSpec
|
||||
module Core
|
||||
module Formatters
|
||||
class HydraFormatter < ProgressFormatter
|
||||
def example_passed(example)
|
||||
output.print "."
|
||||
end
|
||||
|
||||
def example_failed(example)
|
||||
output.print "F"
|
||||
end
|
||||
require 'spec/runner/formatter/progress_bar_formatter'
|
||||
module Spec
|
||||
module Runner
|
||||
module Formatter
|
||||
class HydraFormatter < ProgressBarFormatter
|
||||
# Stifle the post-test summary
|
||||
def dump_summary(duration, example, failure, pending)
|
||||
end
|
||||
|
||||
# Stifle pending specs
|
||||
def dump_pending
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,9 +25,16 @@ module Hydra #:nodoc:
|
|||
# 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
|
||||
# list all the files.
|
||||
def initialize(connection_options, directory, command, timeout = nil)
|
||||
@timeout = timeout
|
||||
@writer, @reader, @error = popen3(%{ssh -tt #{connection_options} 'mkdir -p #{directory} && cd #{directory} && #{command}'})
|
||||
def initialize(connection_options, directory, command)
|
||||
@writer, @reader, @error = popen3("ssh -tt #{connection_options}")
|
||||
@writer.write("cd #{directory}\n")
|
||||
@writer.write(command+"\n")
|
||||
end
|
||||
|
||||
# Close the SSH connection
|
||||
def close
|
||||
@writer.write "exit\n"
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -1,15 +1,10 @@
|
|||
require 'open3'
|
||||
require 'hydra/config'
|
||||
|
||||
module Hydra #:nodoc:
|
||||
# Hydra Task Common attributes and methods
|
||||
class Task
|
||||
# Name of the task. Default 'hydra'
|
||||
attr_accessor :name
|
||||
|
||||
# Command line options
|
||||
attr_accessor :options
|
||||
|
||||
# Files to test.
|
||||
# You can add files manually via:
|
||||
# t.files << [file1, file2, etc]
|
||||
|
@ -36,20 +31,6 @@ module Hydra #:nodoc:
|
|||
# t.listeners << Hydra::Listener::Notifier.new
|
||||
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
|
||||
def find_config_file
|
||||
|
@ -59,7 +40,7 @@ module Hydra #:nodoc:
|
|||
return @config if File.exists?(@config)
|
||||
@config = nil
|
||||
end
|
||||
|
||||
|
||||
# Add files to test by passing in a string to be run through Dir.glob.
|
||||
# For example:
|
||||
#
|
||||
|
@ -78,7 +59,7 @@ module Hydra #:nodoc:
|
|||
# t.add_files 'test/integration/**/*_test.rb'
|
||||
# t.verbose = false # optionally set to true for lots of debug messages
|
||||
# t.autosort = false # disable automatic sorting based on runtime of tests
|
||||
# end
|
||||
# end
|
||||
class TestTask < Hydra::Task
|
||||
|
||||
# Create a new HydraTestTask
|
||||
|
@ -87,30 +68,20 @@ module Hydra #:nodoc:
|
|||
@files = []
|
||||
@verbose = false
|
||||
@autosort = true
|
||||
@serial = false
|
||||
@listeners = [Hydra::Listener::ProgressBar.new]
|
||||
@show_time = true
|
||||
@options = ''
|
||||
|
||||
yield self if block_given?
|
||||
|
||||
# Ensure we override rspec's at_exit
|
||||
if defined?(RSpec)
|
||||
RSpec::Core::Runner.disable_autorun!
|
||||
end
|
||||
require 'hydra/spec/autorun_override'
|
||||
|
||||
unless @serial
|
||||
@config = find_config_file
|
||||
end
|
||||
@config = find_config_file
|
||||
|
||||
@opts = {
|
||||
:verbose => @verbose,
|
||||
:autosort => @autosort,
|
||||
:files => @files,
|
||||
:listeners => @listeners,
|
||||
:environment => @environment,
|
||||
:runner_log_file => @runner_log_file,
|
||||
:options => @options
|
||||
:listeners => @listeners
|
||||
}
|
||||
if @config
|
||||
@opts.merge!(:config => @config)
|
||||
|
@ -126,132 +97,8 @@ module Hydra #:nodoc:
|
|||
def define
|
||||
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
||||
task @name do
|
||||
if Object.const_defined?('Rails') && Rails.env == 'development'
|
||||
$stderr.puts %{WARNING: Rails Environment is "development". Make sure to set it properly (ex: "RAILS_ENV=test rake hydra")}
|
||||
end
|
||||
|
||||
start = Time.now if @show_time
|
||||
|
||||
puts '********************'
|
||||
puts @options.inspect
|
||||
master = Hydra::Master.new(@opts)
|
||||
|
||||
$stdout.puts "\nFinished in #{'%.6f' % (Time.now - start)} seconds." if @show_time
|
||||
|
||||
unless master.failed_files.empty?
|
||||
raise "Hydra: Not all tests passes"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Define a test task that uses hydra to profile your test files
|
||||
#
|
||||
# Hydra::ProfileTask.new('hydra:prof') do |t|
|
||||
# t.add_files 'test/unit/**/*_test.rb'
|
||||
# t.add_files 'test/functional/**/*_test.rb'
|
||||
# t.add_files 'test/integration/**/*_test.rb'
|
||||
# t.generate_html = true # defaults to false
|
||||
# t.generate_text = true # defaults to true
|
||||
# end
|
||||
class ProfileTask < Hydra::Task
|
||||
# boolean: generate html output from ruby-prof
|
||||
attr_accessor :generate_html
|
||||
# boolean: generate text output from ruby-prof
|
||||
attr_accessor :generate_text
|
||||
|
||||
# Create a new Hydra ProfileTask
|
||||
def initialize(name = 'hydra:profile')
|
||||
@name = name
|
||||
@files = []
|
||||
@verbose = false
|
||||
@generate_html = false
|
||||
@generate_text = true
|
||||
|
||||
yield self if block_given?
|
||||
|
||||
# Ensure we override rspec's at_exit
|
||||
require 'hydra/spec/autorun_override'
|
||||
|
||||
@config = find_config_file
|
||||
|
||||
@opts = {
|
||||
:verbose => @verbose,
|
||||
:files => @files
|
||||
}
|
||||
define
|
||||
end
|
||||
|
||||
private
|
||||
# Create the rake task defined by this HydraTestTask
|
||||
def define
|
||||
desc "Hydra Test Profile" + (@name == :hydra ? "" : " for #{@name}")
|
||||
task @name do
|
||||
require 'ruby-prof'
|
||||
RubyProf.start
|
||||
|
||||
runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
|
||||
@files.each do |file|
|
||||
$stdout.write runner.run_file(file)
|
||||
$stdout.flush
|
||||
end
|
||||
|
||||
$stdout.write "\nTests complete. Generating profiling output\n"
|
||||
$stdout.flush
|
||||
|
||||
result = RubyProf.stop
|
||||
|
||||
if @generate_html
|
||||
printer = RubyProf::GraphHtmlPrinter.new(result)
|
||||
out = File.new("ruby-prof.html", 'w')
|
||||
printer.print(out, :min_self => 0.05)
|
||||
out.close
|
||||
$stdout.write "Profiling data written to [ruby-prof.html]\n"
|
||||
end
|
||||
|
||||
if @generate_text
|
||||
printer = RubyProf::FlatPrinter.new(result)
|
||||
out = File.new("ruby-prof.txt", 'w')
|
||||
printer.print(out, :min_self => 0.05)
|
||||
out.close
|
||||
$stdout.write "Profiling data written to [ruby-prof.txt]\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Define a sync task that uses hydra to rsync the source tree under test to remote workers.
|
||||
#
|
||||
# This task is very useful to run before a remote db:reset task to make sure the db/schema.rb
|
||||
# file is up to date on the remote workers.
|
||||
#
|
||||
# Hydra::SyncTask.new('hydra:sync') do |t|
|
||||
# t.verbose = false # optionally set to true for lots of debug messages
|
||||
# end
|
||||
class SyncTask < Hydra::Task
|
||||
# Create a new SyncTestTask
|
||||
def initialize(name = :sync)
|
||||
@name = name
|
||||
@verbose = false
|
||||
|
||||
yield self if block_given?
|
||||
|
||||
@config = find_config_file
|
||||
|
||||
@opts = {
|
||||
:verbose => @verbose
|
||||
}
|
||||
@opts.merge!(:config => @config) if @config
|
||||
|
||||
define
|
||||
end
|
||||
|
||||
private
|
||||
# Create the rake task defined by this HydraSyncTask
|
||||
def define
|
||||
desc "Hydra Tests" + (@name == :hydra ? "" : " for #{@name}")
|
||||
task @name do
|
||||
Hydra::Sync.sync_many(@opts)
|
||||
Hydra::Master.new(@opts)
|
||||
#exit(0) #bypass test on_exit output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -265,9 +112,8 @@ module Hydra #:nodoc:
|
|||
include Open3
|
||||
# Create a new hydra remote task with the given name.
|
||||
# The task will be named hydra:remote:<name>
|
||||
def initialize(name, command=nil)
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@command = command
|
||||
yield self if block_given?
|
||||
@config = find_config_file
|
||||
if @config
|
||||
|
@ -281,42 +127,34 @@ module Hydra #:nodoc:
|
|||
def define
|
||||
desc "Run #{@name} remotely on all workers"
|
||||
task "hydra:remote:#{@name}" do
|
||||
config = Hydra::Config.load(@config)
|
||||
environment = config.fetch('environment') { 'test' }
|
||||
config = YAML.load_file(@config)
|
||||
workers = config.fetch('workers') { [] }
|
||||
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|
|
||||
@listeners << Thread.new do
|
||||
begin
|
||||
@results[worker] = if run_command(worker, @command)
|
||||
"==== #{@name} passed on #{worker['connect']} ====\n"
|
||||
else
|
||||
"==== #{@name} failed on #{worker['connect']} ====\nPlease see above for more details.\n"
|
||||
end
|
||||
rescue
|
||||
@results[worker] = "==== #{@name} failed for #{worker['connect']} ====\n#{$!.inspect}\n#{$!.backtrace.join("\n")}"
|
||||
$stdout.write "==== Hydra Running #{@name} on #{worker['connect']} ====\n"
|
||||
ssh_opts = worker.fetch('ssh_opts') { '' }
|
||||
writer, reader, error = popen3("ssh -tt #{ssh_opts} #{worker['connect']} ")
|
||||
writer.write("cd #{worker['directory']}\n")
|
||||
writer.write "echo BEGIN HYDRA\n"
|
||||
writer.write("RAILS_ENV=test rake #{@name}\n")
|
||||
writer.write "echo END HYDRA\n"
|
||||
writer.write("exit\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
|
||||
$stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n"
|
||||
end
|
||||
@listeners.each{|l| l.join}
|
||||
$stdout.write "\n==== Hydra Running #{@name} COMPLETE ====\n\n"
|
||||
$stdout.write @results.values.join("\n")
|
||||
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
|
||||
|
||||
# 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')
|
||||
#
|
||||
# Allows you to run:
|
||||
#
|
||||
#
|
||||
# rake hydra:db:reset
|
||||
#
|
||||
# Then, db:reset will be run locally and on all remote workers. This
|
||||
|
@ -348,7 +186,7 @@ module Hydra #:nodoc:
|
|||
def define
|
||||
Hydra::RemoteTask.new(@name)
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -9,8 +9,6 @@ module Hydra #:nodoc:
|
|||
class Worker
|
||||
include Hydra::Messages::Worker
|
||||
traceable('WORKER')
|
||||
|
||||
attr_reader :runners
|
||||
# Create a new worker.
|
||||
# * io: The IO object to use to communicate with the master
|
||||
# * num_runners: The number of runners to launch
|
||||
|
@ -19,37 +17,16 @@ module Hydra #:nodoc:
|
|||
@io = opts.fetch(:io) { raise "No IO Object" }
|
||||
@runners = []
|
||||
@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 })
|
||||
@io.write(Hydra::Messages::Worker::WorkerBegin.new)
|
||||
|
||||
process_messages
|
||||
|
||||
|
||||
@runners.each{|r| Process.wait r[:pid] }
|
||||
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.
|
||||
# Then the worker bubbles the file request up to the master.
|
||||
def request_file(message, runner)
|
||||
|
@ -91,10 +68,9 @@ module Hydra #:nodoc:
|
|||
trace "Booting #{num_runners} Runners"
|
||||
num_runners.times do
|
||||
pipe = Hydra::Pipe.new
|
||||
|
||||
child = SafeFork.fork do
|
||||
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
|
||||
pipe.identify_as_parent
|
||||
@runners << { :pid => child, :io => pipe, :idle => false }
|
||||
|
@ -123,7 +99,7 @@ module Hydra #:nodoc:
|
|||
begin
|
||||
message = @io.gets
|
||||
if message and !message.class.to_s.index("Master").nil?
|
||||
trace "Received Message from Master"
|
||||
trace "Received Message from Master"
|
||||
trace "\t#{message.inspect}"
|
||||
message.handle(self)
|
||||
else
|
||||
|
@ -132,7 +108,7 @@ module Hydra #:nodoc:
|
|||
end
|
||||
rescue IOError => ex
|
||||
trace "Worker lost Master"
|
||||
shutdown
|
||||
Thread.exit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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 "<span class="param">HYDRA</span>" 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">"<span class="param">HYDRA</span>" 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>
|
|
@ -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 "<span class="param">HYDRA</span>" 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">"<span class="param">HYDRA</span>" 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>
|
|
@ -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
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
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
|
||||
|
||||
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
|
||||
|
||||
When /^I write "([^\"]*)" to the file$/ do |text|
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
require '../test/fixtures/runner_listeners.rb'
|
||||
require '../test/fixtures/master_listeners.rb'
|
|
@ -1,4 +0,0 @@
|
|||
"use strict";
|
||||
var thisvar;
|
||||
var thatvar
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"var1": "something",
|
||||
"var2": "trailing comma",
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,5 +1,3 @@
|
|||
require 'rubygems'
|
||||
gem 'test-unit'
|
||||
require 'test/unit'
|
||||
|
||||
class SyncTest < Test::Unit::TestCase
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
workers:
|
||||
- type: ssh
|
||||
connect: localhost
|
||||
directory: /tmp
|
||||
runners: 1
|
|
@ -2,7 +2,7 @@ require File.join(File.dirname(__FILE__), '..', 'test_helper')
|
|||
|
||||
class WriteFileTest < Test::Unit::TestCase
|
||||
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"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
require 'rspec'
|
||||
require 'hydra/tmpdir'
|
||||
describe "file writing" do
|
||||
require 'tmpdir'
|
||||
require 'spec'
|
||||
context "file writing" 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"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
require 'rspec'
|
||||
require 'hydra/tmpdir'
|
||||
describe "file writing" do
|
||||
require 'tmpdir'
|
||||
require 'spec'
|
||||
context "file writing" 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"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
require 'test_helper'
|
||||
require 'fixtures/runner_listeners'
|
||||
require 'fixtures/master_listeners'
|
||||
require File.join(File.dirname(__FILE__), 'test_helper')
|
||||
|
||||
class MasterTest < Test::Unit::TestCase
|
||||
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)
|
||||
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
|
||||
Hydra::Master.new(:files => [test_file])
|
||||
assert File.exists?(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 report = YAML.load_file(report_file)
|
||||
assert_not_nil report[test_file]
|
||||
|
@ -119,8 +75,8 @@ class MasterTest < Test::Unit::TestCase
|
|||
:workers => [{
|
||||
:type => :ssh,
|
||||
:connect => 'localhost',
|
||||
:directory => remote_dir_path,
|
||||
:runners => 1
|
||||
:directory => File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')),
|
||||
:runners => 1
|
||||
}]
|
||||
)
|
||||
assert File.exists?(target_file)
|
||||
|
@ -137,8 +93,8 @@ class MasterTest < Test::Unit::TestCase
|
|||
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')
|
||||
local = File.join(Dir.tmpdir, 'hydra', 'local')
|
||||
remote = File.join(Dir.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}
|
||||
|
||||
|
@ -167,7 +123,6 @@ class MasterTest < Test::Unit::TestCase
|
|||
:type => :ssh,
|
||||
:connect => 'localhost',
|
||||
:directory => remote,
|
||||
:verbose => true,
|
||||
:runners => 1
|
||||
}],
|
||||
:sync => {
|
||||
|
@ -183,225 +138,4 @@ class MasterTest < Test::Unit::TestCase
|
|||
assert !File.exists?(File.join(remote, 'test_b.rb')), "B was not deleted"
|
||||
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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require 'test_helper'
|
||||
require File.join(File.dirname(__FILE__), 'test_helper')
|
||||
|
||||
class MessageTest < Test::Unit::TestCase
|
||||
class MyMessage < Hydra::Message
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require 'test_helper'
|
||||
require File.join(File.dirname(__FILE__), 'test_helper')
|
||||
|
||||
class PipeTest < Test::Unit::TestCase
|
||||
context "a pipe" do
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
require 'test_helper'
|
||||
require 'fixtures/runner_listeners'
|
||||
require File.join(File.dirname(__FILE__), 'test_helper')
|
||||
|
||||
class RunnerTest < Test::Unit::TestCase
|
||||
context "with a file to test and a destination to verify" do
|
||||
|
@ -38,20 +37,8 @@ class RunnerTest < Test::Unit::TestCase
|
|||
Process.wait(child)
|
||||
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
|
||||
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)
|
||||
assert File.exists?(target_file)
|
||||
assert_equal "HYDRA", File.read(target_file)
|
||||
|
@ -61,110 +48,58 @@ class RunnerTest < Test::Unit::TestCase
|
|||
runner.run_file(alternate_rspec_file)
|
||||
assert File.exists?(alternate_target_file)
|
||||
assert_equal "HYDRA", File.read(alternate_target_file)
|
||||
assert !File.exists?(target_file), "Tests are double running!"
|
||||
end
|
||||
|
||||
should "run rspec tests with pending examples" do
|
||||
runner = Hydra::Runner.new(:options => {}, :io => File.new('/dev/null', 'w'))
|
||||
assert File.exists?(rspec_file_with_pending)
|
||||
|
||||
runner.run_file(rspec_file_with_pending)
|
||||
|
||||
assert File.exists?(target_file)
|
||||
assert_equal "HYDRA", File.read(target_file)
|
||||
|
||||
FileUtils.rm_f(target_file)
|
||||
assert !File.exists?(target_file)
|
||||
end
|
||||
|
||||
should "run two cucumber tests" do
|
||||
# because of all the crap cucumber pulls in
|
||||
# we run this in a fork to not contaminate
|
||||
# the main test environment
|
||||
capture_stderr do # redirect stderr
|
||||
pid = Process.fork do
|
||||
runner = Hydra::Runner.new(:options => {}, :io => File.new('/dev/null', 'w'))
|
||||
runner.run_file(cucumber_feature_file)
|
||||
assert File.exists?(target_file)
|
||||
assert_equal "HYDRA", File.read(target_file)
|
||||
pid = Process.fork do
|
||||
puts "THE FOLLOWING WARNINGS CAN BE IGNORED"
|
||||
puts "It is caused by Cucumber loading all rb files near its features"
|
||||
|
||||
FileUtils.rm_f(target_file)
|
||||
|
||||
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)
|
||||
end
|
||||
Process.wait pid
|
||||
runner = Hydra::Runner.new(:io => File.new('/dev/null', 'w'))
|
||||
runner.run_file(cucumber_feature_file)
|
||||
assert File.exists?(target_file)
|
||||
assert_equal "HYDRA", File.read(target_file)
|
||||
|
||||
FileUtils.rm_f(target_file)
|
||||
|
||||
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
|
||||
Process.wait pid
|
||||
end
|
||||
|
||||
should "be able to run a runner over ssh" do
|
||||
ssh = Hydra::SSH.new(
|
||||
'localhost -o ControlMaster=no',
|
||||
'localhost',
|
||||
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)
|
||||
ssh.write(Hydra::Messages::Worker::RunFile.new(:file => test_file))
|
||||
|
||||
|
||||
# grab its response. This makes us wait for it to finish
|
||||
echo = ssh.gets # get the ssh echo
|
||||
response = ssh.gets
|
||||
|
||||
assert_equal Hydra::Messages::Runner::Results, response.class
|
||||
|
||||
|
||||
# tell it to shut down
|
||||
ssh.write(Hydra::Messages::Worker::Shutdown.new)
|
||||
|
||||
ssh.close
|
||||
|
||||
|
||||
# ensure it ran
|
||||
assert File.exists?(target_file)
|
||||
assert_equal "HYDRA", File.read(target_file)
|
||||
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
|
||||
|
||||
module RunnerTestHelper
|
||||
|
@ -177,6 +112,7 @@ class RunnerTest < Test::Unit::TestCase
|
|||
|
||||
# grab its response. This makes us wait for it to finish
|
||||
response = pipe.gets
|
||||
puts response.output
|
||||
|
||||
# tell it to shut down
|
||||
pipe.write(Hydra::Messages::Worker::Shutdown.new)
|
||||
|
@ -186,9 +122,9 @@ class RunnerTest < Test::Unit::TestCase
|
|||
assert_equal "HYDRA", File.read(target_file)
|
||||
end
|
||||
|
||||
def run_the_runner(pipe, listeners = [])
|
||||
def run_the_runner(pipe)
|
||||
pipe.identify_as_child
|
||||
Hydra::Runner.new( :io => pipe, :options => {}, :runner_listeners => listeners )
|
||||
Hydra::Runner.new(:io => pipe)
|
||||
end
|
||||
end
|
||||
include RunnerTestHelper
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
require 'test_helper'
|
||||
require File.join(File.dirname(__FILE__), 'test_helper')
|
||||
|
||||
class SSHTest < Test::Unit::TestCase
|
||||
should "be able to execute a command over ssh" do
|
||||
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
|
||||
"ruby fixtures/hello_world.rb"
|
||||
)
|
||||
|
@ -11,16 +11,4 @@ class SSHTest < Test::Unit::TestCase
|
|||
assert_equal "Hello World", response.text
|
||||
ssh.close
|
||||
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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,24 +1,22 @@
|
|||
require 'rubygems'
|
||||
gem 'test-unit'
|
||||
require 'test/unit'
|
||||
require 'shoulda'
|
||||
require 'tmpdir'
|
||||
require "stringio"
|
||||
|
||||
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
||||
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
||||
require 'hydra'
|
||||
|
||||
# Since Hydra turns off testing, we have to turn it back on
|
||||
#Test::Unit.run = false
|
||||
Test::Unit.run = false
|
||||
|
||||
class Test::Unit::TestCase
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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'))
|
||||
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
|
||||
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_file.feature'))
|
||||
end
|
||||
|
@ -44,50 +38,6 @@ class Test::Unit::TestCase
|
|||
def alternate_cucumber_feature_file
|
||||
File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'features', 'write_alternate_file.feature'))
|
||||
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
|
||||
|
||||
module Hydra #:nodoc:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require 'test_helper'
|
||||
require File.join(File.dirname(__FILE__), 'test_helper')
|
||||
|
||||
class WorkerTest < Test::Unit::TestCase
|
||||
context "with a file to test and a destination to verify" do
|
||||
|
@ -36,15 +36,13 @@ class WorkerTest < Test::Unit::TestCase
|
|||
module WorkerTestHelper
|
||||
def run_the_worker(pipe, num_runners)
|
||||
pipe.identify_as_child
|
||||
Hydra::Worker.new({:io => pipe, :runners => num_runners, :options => {}})
|
||||
Hydra::Worker.new({:io => pipe, :runners => num_runners})
|
||||
end
|
||||
|
||||
def request_a_file_and_verify_completion(pipe, num_runners)
|
||||
pipe.identify_as_parent
|
||||
pipe.gets # grab the WorkerBegin
|
||||
num_runners.times do
|
||||
response = pipe.gets # grab the RequestFile
|
||||
assert response.is_a?(Hydra::Messages::Worker::RequestFile), "Expected RequestFile but got #{response.class.to_s}"
|
||||
assert pipe.gets.is_a?(Hydra::Messages::Worker::RequestFile)
|
||||
end
|
||||
pipe.write(Hydra::Messages::Master::RunFile.new(:file => test_file))
|
||||
|
||||
|
|
Loading…
Reference in New Issue