Top Level Namespace
Defined Under Namespace
Constant Summary collapse
- ALREADY_CONFIGURED =
<<'EOF' tddium has already been initialized. (settings are in %s) Use 'tddium config:reset' to clear configuration, and then run 'tddium config:init' again. EOF
- CONFIG_DEFAULTS =
{ :aws_key => nil, :aws_secret => nil, :test_pattern => '**/*_test.rb', :key_name => nil, :key_directory => nil, :result_directory => 'results', :ssh_tunnel => false, :require_files => nil, }
- AMI_NAME =
'ami-da13e3b3'
- DEV_SESSION_KEY =
'dev'
- REPORT_FILENAME =
"selenium_report.html"
Class Method Summary collapse
Instance Method Summary collapse
- #checkstart_dev_instance ⇒ Object
- #collect_syslog(target_directory = '.') ⇒ Object
- #default_report_path ⇒ Object
- #find_config ⇒ Object
-
#find_instances ⇒ Object
Find all instances running the tddium AMI.
- #find_test_files(pattern = nil) ⇒ Object
- #get_config_paths ⇒ Object
- #get_keyfile ⇒ Object
- #init_task ⇒ Object
-
#key_file_name(config) ⇒ Object
Compute the name of the ssh private key file from configured parameters.
- #kill_tunnel ⇒ Object
- #make_spec_cmd(tests, environment, result_path) ⇒ Object
- #make_ssh_tunnel(key_file, server) ⇒ Object
- #parallel_task(args) ⇒ Object
-
#prepare_task(args) ⇒ Object
Portions of this file derived from spec_storm, under the following license:.
- #read_config ⇒ Object
- #recycle_dev ⇒ Object
- #recycle_server(dns_name) ⇒ Object
- #recycle_servers ⇒ Object
- #remote_cmd(host, cmd) ⇒ Object
- #remote_cp(host, remote_file, local_file) ⇒ Object
- #reset_task ⇒ Object
-
#result_directory ⇒ Object
Prepare the result directory, as specified by config.
- #session_instances(session_key) ⇒ Object
- #setup_environment(server) ⇒ Object
- #setup_task(args) ⇒ Object
- #spec_opts(result_path) ⇒ Object
-
#ssh_tunnel(hostname) ⇒ Object
Subprocess main body to create an ssh tunnel to hostname for selenium, binding remote:4444 to local:4444.
-
#start_instance(session_key = nil) ⇒ Object
Start and setup an EC2 instance to run a selenium-grid node.
- #stop_all_instances ⇒ Object
-
#stop_instance(session_key = nil) ⇒ Object
Stop the instance created by start_instance.
- #test_batches(num_batches) ⇒ Object
- #wait_for_selenium(hostname) ⇒ Object
- #write_config(config) ⇒ Object
Class Method Details
.execute_command(cmd) ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/tddium/parallel.rb', line 77 def self.execute_command(cmd) STDERR.puts "Running '#{cmd}'" f = open("|#{cmd}") all = '' while out = f.gets(".") all+=out print out STDOUT.flush end all end |
Instance Method Details
#checkstart_dev_instance ⇒ Object
145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/tddium/instance.rb', line 145 def checkstart_dev_instance conf = read_config dev_servers = session_instances(DEV_SESSION_KEY) if dev_servers.length > 0 STDERR.puts "Using existing server #{dev_servers[0].dns_name}." setup_environment(dev_servers[0]) return dev_servers[0] else STDERR.puts "Starting EC2 Instance" server = start_instance(DEV_SESSION_KEY) sleep 30 return server end end |
#collect_syslog(target_directory = '.') ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/tddium/reporting.rb', line 30 def collect_syslog(target_directory='.') keyfile = get_keyfile if keyfile.nil? raise "No ssh keyfile configured. Can't connect to remote" end instances = session_instances(@tddium_session ? @tddium_session : DEV_SESSION_KEY) instances.each do |inst| %w(selenium-hub selenium-rc).each do |log| remote_cp(inst.dns_name, "/var/log/#{log}.log", File.join(target_directory, "#{log}.#{inst.dns_name}")) end end end |
#default_report_path ⇒ Object
26 27 28 |
# File 'lib/tddium/reporting.rb', line 26 def default_report_path File.join(read_config[:result_directory], 'latest', REPORT_FILENAME) end |
#find_config ⇒ Object
40 41 42 43 |
# File 'lib/tddium/config.rb', line 40 def find_config get_config_paths.each {|f| return f if File.exists?(f)} nil end |
#find_instances ⇒ Object
Find all instances running the tddium AMI
161 162 163 164 165 166 167 168 169 170 |
# File 'lib/tddium/instance.rb', line 161 def find_instances conf = read_config @ec2pool = Fog::AWS::Compute.new(:aws_access_key_id => conf[:aws_key], :aws_secret_access_key => conf[:aws_secret]) @ec2pool.servers.select do |s| s.image_id == AMI_NAME && !%w{terminated stopped shutting-down}.include?(s.state) end end |
#find_test_files(pattern = nil) ⇒ Object
130 131 132 133 134 |
# File 'lib/tddium/config.rb', line 130 def find_test_files(pattern=nil) conf = read_config pattern ||= conf[:test_pattern] Dir[pattern].sort end |
#get_config_paths ⇒ Object
24 25 26 27 28 29 30 31 32 |
# File 'lib/tddium/config.rb', line 24 def get_config_paths paths = [] if ENV['RAILS_ROOT'] paths << File.join(ENV['RAILS_ROOT'], '.tddium') end paths << File.('~/.tddium') paths << '.tddium' paths end |
#get_keyfile ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/tddium/config.rb', line 113 def get_keyfile conf = read_config key_file = key_file_name(conf) if key_file.nil? return nil elsif !File.exists?(key_file) STDERR.puts "No key file #{key_file} present" return nil elsif (File.stat(key_file).mode & "77".to_i(8) != 0) mode =File.stat(key_file).mode STDERR.puts "Keyfile has wrong perms: #{mode.to_s}. should be x00" return nil else return key_file end end |
#init_task ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/tddium/config.rb', line 57 def init_task path = find_config if path puts ALREADY_CONFIGURED % path else conf = {} conf[:aws_key] = ask('Enter AWS Access Key: ') conf[:aws_secret] = ask('Enter AWS Secret: ') conf[:test_pattern] = ask('Enter filepattern for tests: ') { |q| q.default='**/*_spec.rb' } conf[:key_directory] = ask('Enter directory for secret key(s): ') { |q| q.default='spec/secret' } conf[:key_name] = ask('Enter secret key name (excluding .pem suffix): ') { |q| q.default='sg-keypair' } conf[:result_directory] = ask('Enter directory for result reports: ') { |q| q.default='results' } conf[:server_tag] = ask("(optional) Enter tag=value to give instances: ") conf[:ssh_tunnel] = ask("Create ssh tunnel to hub at localhost:4444:") { |q| q.default=false } conf[:require_files] = ask("(optional) Filenames (comma-separated) for spec to require:") write_config conf end end |
#key_file_name(config) ⇒ Object
Compute the name of the ssh private key file from configured parameters
105 106 107 108 109 110 111 |
# File 'lib/tddium/config.rb', line 105 def key_file_name(config) if config[:key_name].nil? || config[:key_directory].nil? return nil end File.join(config[:key_directory], "#{config[:key_name]}.pem") end |
#kill_tunnel ⇒ Object
30 31 32 33 34 35 36 |
# File 'lib/tddium/ssh.rb', line 30 def kill_tunnel if !$tunnel_pid.nil? Process.kill("TERM", $tunnel_pid) Process.waitpid($tunnel_pid) $tunnel_pid = nil end end |
#make_spec_cmd(tests, environment, result_path) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/tddium/parallel.rb', line 8 def make_spec_cmd(tests, environment, result_path) if !result_path raise "result_path must be specified" end environ = { "RAILS_ENV" => environment, 'RSPEC_COLOR' => $stdout.tty? ? 1 : nil } env_str = environ.map{|k,v| "#{k}=#{v}" if v}.join(' ') cmd = "env #{env_str} " cmd << "spec " cmd << "#{tests.map{|x| "\"#{x}\""}.join(' ')} " cmd << spec_opts(result_path).join(' ') cmd end |
#make_ssh_tunnel(key_file, server) ⇒ Object
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/tddium/ssh.rb', line 19 def make_ssh_tunnel(key_file, server) $tunnel_pid = nil if !key_file.nil? then $tunnel_pid = Process.fork do ssh_tunnel(server.dns_name) end STDERR.puts "Created ssh tunnel to #{server.dns_name}:4444 at localhost:4444 [pid #{$tunnel_pid}]" end end |
#parallel_task(args) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/tddium/parallel.rb', line 45 def parallel_task(args) args.with_defaults(:threads => 5, :environment => "selenium", :result_directory => '.') threads = args.threads.to_i STDERR.puts args.inspect latest = args.result_directory puts "Running tests. Results will be in #{latest}" puts "Started at #{Time.now.inspect}" output = {} batches = test_batches(threads) num_threads = batches.size Parallel.in_threads(num_threads) do |i| if batches[i].size > 0 result_path = File.join(latest, "#{i}-#{REPORT_FILENAME}") cmd = make_spec_cmd(batches[i], args.environment, result_path) output.merge!({"#{batches[i].inspect}" => execute_command( cmd )}) #puts "Running results: #{output.inspect}" end end puts "Results:" output.each do |key, value| puts ">>>>>>>> #{key}" puts value end puts "Finished at #{Time.now.inspect}" end |
#prepare_task(args) ⇒ Object
Portions of this file derived from spec_storm, under the following license:
Copyright © 2010 Sauce Labs Inc
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/tddium/rails.rb', line 33 def prepare_task(args) args.with_defaults(:environment => "selenium") tests = find_test_files() puts "\t#{tests.size} test files" first = true tests.each do |test| prefix = SpecStorm::db_prefix_for(test) puts "Migrating another set of tables..." puts "Generating DB_PREFIX: #{test} -> #{prefix}" if first == true ["export DB_PREFIX=#{prefix}; rake db:drop RAILS_ENV=#{args.environment} --trace", "export DB_PREFIX=#{prefix}; rake db:create RAILS_ENV=#{args.environment} --trace"].each do |command| IO.popen( command ).close end end ["export DB_PREFIX=#{prefix}; rake db:migrate RAILS_ENV=#{args.environment} --trace"].each do |cmd| IO.popen( cmd ).close end first = false end end |
#read_config ⇒ Object
98 99 100 101 102 |
# File 'lib/tddium/config.rb', line 98 def read_config path = find_config file_conf = path ? YAML.load(File.read(path)) : {} CONFIG_DEFAULTS.merge(file_conf) end |
#recycle_dev ⇒ Object
135 136 137 138 139 140 141 142 143 |
# File 'lib/tddium/instance.rb', line 135 def recycle_dev dev_servers = session_instances(DEV_SESSION_KEY) if dev_servers.length > 0 then recycle_server(dev_servers[0].dns_name) else STDERR.puts "No dev servers found" end end |
#recycle_server(dns_name) ⇒ Object
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/tddium/instance.rb', line 112 def recycle_server(dns_name) conf = read_config keyfile = get_keyfile if keyfile.nil? raise "No keyfile. Can't execute remote commands" end remote_cmd(dns_name, "sudo killall -9 java") remote_cmd(dns_name, "bin/launch-hub.sh") wait_for_selenium(dns_name) remote_cmd(dns_name, "bin/launch-rc.sh") sleep 5 STDERR.puts "Recycled Selenium on instance: #{dns_name}" end |
#recycle_servers ⇒ Object
128 129 130 131 132 133 |
# File 'lib/tddium/instance.rb', line 128 def recycle_servers servers = find_instances servers.each do |s| recycle_server(s.dns_name) end end |
#remote_cmd(host, cmd) ⇒ Object
39 40 41 42 43 |
# File 'lib/tddium/ssh.rb', line 39 def remote_cmd(host, cmd) key_file = get_keyfile system("ssh -o 'StrictHostKeyChecking no' -i #{key_file} ec2-user@#{host} '#{cmd}'") end |
#remote_cp(host, remote_file, local_file) ⇒ Object
45 46 47 48 |
# File 'lib/tddium/ssh.rb', line 45 def remote_cp(host, remote_file, local_file) key_file = get_keyfile system("scp -o 'StrictHostKeyChecking no' -i #{key_file} ec2-user@#{host}:#{remote_file} #{local_file}") end |
#reset_task ⇒ Object
45 46 47 48 49 50 51 52 53 |
# File 'lib/tddium/config.rb', line 45 def reset_task config_path = find_config config = read_config if config_path puts "Deleting old configuration in #{config_path}:" puts config.inspect rm config_path, :force => true end end |
#result_directory ⇒ Object
Prepare the result directory, as specified by config.
If the directory doesn’t exist create it, and a latest subdirectory.
If the latest subdirectory exists, rotate it and create a new empty latest.
11 12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/tddium/reporting.rb', line 11 def result_directory conf = read_config latest = File.join(conf[:result_directory], 'latest') if File.directory?(latest) then mtime = File.stat(latest).mtime.strftime("%Y%m%d-%H%M%S") archive = File.join(conf[:result_directory], mtime) FileUtils.mv(latest, archive) end FileUtils.mkdir_p latest latest end |
#session_instances(session_key) ⇒ Object
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/tddium/instance.rb', line 172 def session_instances(session_key) servers = find_instances if servers.nil? return nil else session_servers = [] servers.each do |s| # in Fog 0.3.33, :filters is buggy and won't accept resourceId or resource_id = @ec2pool.(:filters => {:key => 'tddium_session'}).select{|t| t.resource_id == s.id} if .first and .first.value == session_key then STDERR.puts "selecting instance #{s.id} #{s.dns_name} from our session" session_servers << s else STDERR.puts "skipping instance #{s.id} #{s.dns_name} created in another session" end end return session_servers end end |
#setup_environment(server) ⇒ Object
17 18 19 20 21 22 23 24 |
# File 'lib/tddium/instance.rb', line 17 def setup_environment(server) if !$tunnel_pid.nil? ENV['SELENIUM_RC_HOST'] = 'localhost' else ENV['SELENIUM_RC_HOST'] = server.dns_name end ENV['TDDIUM'] = '1' end |
#setup_task(args) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/tddium/rails.rb', line 60 def setup_task(args) args.with_defaults(:environment => "selenium") File.copy(File.join(RAILS_ROOT, 'config', 'environments', 'test.rb'), File.join(RAILS_ROOT, 'config', 'environments', "#{args.environment}.rb")) open(File.join(RAILS_ROOT, 'config', 'environments', "#{args.environment}.rb"), 'a') do |f| f.puts "\nmodule SpecStorm" f.puts " USE_NAMESPACE_HACK = true" f.puts "end" end end |
#spec_opts(result_path) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/tddium/config.rb', line 136 def spec_opts(result_path) conf = read_config s = [] s << '--color' s << "--require '#{conf[:require_files]}'" if conf[:require_files] s << "--require 'rubygems,selenium/rspec/reporting/selenium_test_report_formatter'" s << "--format=Selenium::RSpec::SeleniumTestReportFormatter:#{result_path}" s << "--format=progress" s << ENV['TDDIUM_SPEC_OPTS'] if ENV['TDDIUM_SPEC_OPTS'] s end |
#ssh_tunnel(hostname) ⇒ Object
Subprocess main body to create an ssh tunnel to hostname for selenium, binding remote:4444 to local:4444. Authenticate with the private key in key_file.
The ssh tunnel will auto-accept the remote host key.
9 10 11 12 13 14 15 16 17 |
# File 'lib/tddium/ssh.rb', line 9 def ssh_tunnel(hostname) ssh_up = false tries = 0 while !ssh_up && tries < 3 sleep 3 ssh_up = remote_cmd(hostname, "-L 4444:#{hostname}:4444 -N") tries += 1 end end |
#start_instance(session_key = nil) ⇒ Object
Start and setup an EC2 instance to run a selenium-grid node. Set the tddium_session tag to session_key, if it’s specified.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/tddium/instance.rb', line 28 def start_instance(session_key=nil) conf = read_config if session_key.nil? @tddium_session = rand(2**64-1).to_s(36) else @tddium_session = session_key end key_file = get_keyfile @ec2pool = Fog::AWS::Compute.new(:aws_access_key_id => conf[:aws_key], :aws_secret_access_key => conf[:aws_secret]) server = @ec2pool.servers.create(:flavor_id => 'm1.large', :groups => ['selenium-grid'], :image_id => AMI_NAME, :name => 'sg-server', :key_name => conf[:key_name]) server.wait_for { ready? } server.reload @ec2pool..create(:key => 'tddium_session', :value => @tddium_session, :resource_id => server.id) if conf.include?(:server_tag) then server_tag = conf[:server_tag].split('=') @ec2pool..create(:key => server_tag[0], :value => server_tag[1], :resource_id => server.id) end puts "started instance #{server.id} #{server.dns_name} in group #{server.groups} with tags #{server..inspect}" if conf[:ssh_tunnel] == "true" make_ssh_tunnel(key_file, server) end setup_environment(server) uri = wait_for_selenium(ENV['SELENIUM_RC_HOST']) puts "Selenium Console:" puts "#{uri}" if !key_file.nil? STDERR.puts "You can login via \"ssh -i #{key_file} ec2-user@#{server.dns_name}\"" STDERR.puts "Making /var/log/messages world readable" remote_cmd(server.dns_name, "sudo chmod 644 /var/log/messages") else # TODO: Remove when /var/log/messages bug is fixed STDERR.puts "No key_file provided. /var/log/messages may not be readable by ec2-user." end server end |
#stop_all_instances ⇒ Object
192 193 194 195 196 197 198 |
# File 'lib/tddium/instance.rb', line 192 def stop_all_instances servers = find_instances servers.each do |s| STDERR.puts "stopping instance #{s.id} #{s.dns_name}" s.destroy end end |
#stop_instance(session_key = nil) ⇒ Object
Stop the instance created by start_instance
201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/tddium/instance.rb', line 201 def stop_instance(session_key=nil) conf = read_config kill_tunnel servers = session_instances(session_key ? session_key : @tddium_session) servers.each do |s| STDERR.puts "stopping instance #{s.id} #{s.dns_name} from our session" s.destroy end nil end |
#test_batches(num_batches) ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/tddium/parallel.rb', line 26 def test_batches(num_batches) tests = find_test_files STDERR.puts "\t#{tests.size} test files" chunk_size = tests.size / num_batches remainder = tests.size % num_batches batches = [] num_batches.times do |c| if c < remainder batches << tests[((c*chunk_size)+c),(chunk_size+1)] else batches << tests[((c*chunk_size)+remainder),chunk_size] end end STDERR.puts batches.inspect batches end |
#wait_for_selenium(hostname) ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/tddium/instance.rb', line 89 def wait_for_selenium(hostname) uri = URI.parse("http://#{hostname}:4444/console") http = Net::HTTP.new(uri.host, uri.port) http.open_timeout = 60 http.read_timeout = 60 rc_up = false tries = 0 while !rc_up && tries < 5 begin http.request(Net::HTTP::Get.new(uri.request_uri)) rc_up = true rescue Errno::ECONNREFUSED sleep 5 rescue Timeout::Error ensure tries += 1 end end raise "Couldn't connect to #{uri.request_uri}" unless rc_up uri end |
#write_config(config) ⇒ Object
34 35 36 37 38 |
# File 'lib/tddium/config.rb', line 34 def write_config(config) File.open(get_config_paths[0], 'w', 0600) do |f| YAML.dump(config, f) end end |