tddium-old 0.4.7

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