beaker 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +14 -0
  5. data/DOCUMENTING.md +167 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +17 -0
  8. data/README.md +332 -0
  9. data/Rakefile +121 -0
  10. data/beaker.gemspec +42 -0
  11. data/beaker.rb +10 -0
  12. data/bin/beaker +9 -0
  13. data/lib/beaker.rb +36 -0
  14. data/lib/beaker/answers.rb +29 -0
  15. data/lib/beaker/answers/version28.rb +104 -0
  16. data/lib/beaker/answers/version30.rb +194 -0
  17. data/lib/beaker/cli.rb +113 -0
  18. data/lib/beaker/command.rb +241 -0
  19. data/lib/beaker/command_factory.rb +21 -0
  20. data/lib/beaker/dsl.rb +85 -0
  21. data/lib/beaker/dsl/assertions.rb +87 -0
  22. data/lib/beaker/dsl/helpers.rb +625 -0
  23. data/lib/beaker/dsl/install_utils.rb +299 -0
  24. data/lib/beaker/dsl/outcomes.rb +99 -0
  25. data/lib/beaker/dsl/roles.rb +97 -0
  26. data/lib/beaker/dsl/structure.rb +63 -0
  27. data/lib/beaker/dsl/wrappers.rb +100 -0
  28. data/lib/beaker/host.rb +193 -0
  29. data/lib/beaker/host/aix.rb +15 -0
  30. data/lib/beaker/host/aix/file.rb +16 -0
  31. data/lib/beaker/host/aix/group.rb +35 -0
  32. data/lib/beaker/host/aix/user.rb +32 -0
  33. data/lib/beaker/host/unix.rb +54 -0
  34. data/lib/beaker/host/unix/exec.rb +15 -0
  35. data/lib/beaker/host/unix/file.rb +16 -0
  36. data/lib/beaker/host/unix/group.rb +40 -0
  37. data/lib/beaker/host/unix/pkg.rb +22 -0
  38. data/lib/beaker/host/unix/user.rb +32 -0
  39. data/lib/beaker/host/windows.rb +44 -0
  40. data/lib/beaker/host/windows/exec.rb +18 -0
  41. data/lib/beaker/host/windows/file.rb +15 -0
  42. data/lib/beaker/host/windows/group.rb +36 -0
  43. data/lib/beaker/host/windows/pkg.rb +26 -0
  44. data/lib/beaker/host/windows/user.rb +32 -0
  45. data/lib/beaker/hypervisor.rb +37 -0
  46. data/lib/beaker/hypervisor/aixer.rb +52 -0
  47. data/lib/beaker/hypervisor/blimper.rb +123 -0
  48. data/lib/beaker/hypervisor/fusion.rb +56 -0
  49. data/lib/beaker/hypervisor/solaris.rb +65 -0
  50. data/lib/beaker/hypervisor/vagrant.rb +118 -0
  51. data/lib/beaker/hypervisor/vcloud.rb +175 -0
  52. data/lib/beaker/hypervisor/vsphere.rb +80 -0
  53. data/lib/beaker/hypervisor/vsphere_helper.rb +200 -0
  54. data/lib/beaker/logger.rb +167 -0
  55. data/lib/beaker/network_manager.rb +73 -0
  56. data/lib/beaker/options_parsing.rb +323 -0
  57. data/lib/beaker/result.rb +55 -0
  58. data/lib/beaker/shared.rb +15 -0
  59. data/lib/beaker/shared/error_handler.rb +17 -0
  60. data/lib/beaker/shared/host_handler.rb +46 -0
  61. data/lib/beaker/shared/repetition.rb +28 -0
  62. data/lib/beaker/ssh_connection.rb +198 -0
  63. data/lib/beaker/test_case.rb +225 -0
  64. data/lib/beaker/test_config.rb +148 -0
  65. data/lib/beaker/test_suite.rb +288 -0
  66. data/lib/beaker/utils.rb +7 -0
  67. data/lib/beaker/utils/ntp_control.rb +42 -0
  68. data/lib/beaker/utils/repo_control.rb +92 -0
  69. data/lib/beaker/utils/setup_helper.rb +77 -0
  70. data/lib/beaker/utils/validator.rb +27 -0
  71. data/spec/beaker/command_spec.rb +94 -0
  72. data/spec/beaker/dsl/assertions_spec.rb +104 -0
  73. data/spec/beaker/dsl/helpers_spec.rb +230 -0
  74. data/spec/beaker/dsl/install_utils_spec.rb +70 -0
  75. data/spec/beaker/dsl/outcomes_spec.rb +43 -0
  76. data/spec/beaker/dsl/roles_spec.rb +86 -0
  77. data/spec/beaker/dsl/structure_spec.rb +60 -0
  78. data/spec/beaker/dsl/wrappers_spec.rb +52 -0
  79. data/spec/beaker/host_spec.rb +95 -0
  80. data/spec/beaker/logger_spec.rb +117 -0
  81. data/spec/beaker/options_parsing_spec.rb +37 -0
  82. data/spec/beaker/puppet_command_spec.rb +128 -0
  83. data/spec/beaker/ssh_connection_spec.rb +39 -0
  84. data/spec/beaker/test_case_spec.rb +6 -0
  85. data/spec/beaker/test_suite_spec.rb +44 -0
  86. data/spec/mocks_and_helpers.rb +34 -0
  87. data/spec/spec_helper.rb +15 -0
  88. metadata +359 -0
@@ -0,0 +1,148 @@
1
+ require 'open-uri'
2
+ require 'yaml'
3
+
4
+ module Beaker
5
+ # Config was taken by Ruby.
6
+ class TestConfig
7
+
8
+ attr_accessor :logger
9
+ def initialize(config_file, options)
10
+ @options = options
11
+ @logger = options[:logger]
12
+ @config = load_file(config_file)
13
+ end
14
+
15
+ def [](key)
16
+ @config[key]
17
+ end
18
+
19
+ def ssh_defaults
20
+ {
21
+ :config => false,
22
+ :paranoid => false,
23
+ :timeout => 300,
24
+ :auth_methods => ["publickey"],
25
+ :keys => [@options[:keyfile]],
26
+ :port => 22,
27
+ :user_known_hosts_file => "#{ENV['HOME']}/.ssh/known_hosts",
28
+ :forward_agent => true
29
+ }
30
+ end
31
+
32
+ def load_file(config_file)
33
+ if config_file.is_a? Hash
34
+ config = config_file
35
+ else
36
+ config = YAML.load_file(config_file)
37
+
38
+ # Make sure the roles array is present for all hosts
39
+ config['HOSTS'].each_key do |host|
40
+ config['HOSTS'][host]['roles'] ||= []
41
+ end
42
+ end
43
+
44
+ # Merge some useful date into the config hash
45
+ config['CONFIG'] ||= {}
46
+ consoleport = ENV['consoleport'] || config['CONFIG']['consoleport'] || 443
47
+ config['CONFIG']['consoleport'] = consoleport.to_i
48
+ config['CONFIG']['ssh'] = ssh_defaults.merge(config['CONFIG']['ssh'] || {})
49
+ config['CONFIG']['modules'] = @options[:modules] || []
50
+
51
+ if is_pe?
52
+ config['CONFIG']['pe_dir'] = puppet_enterprise_dir
53
+ config['CONFIG']['pe_ver'] = puppet_enterprise_version
54
+ config['CONFIG']['pe_ver_win'] = puppet_enterprise_version_win
55
+ else
56
+ config['CONFIG']['puppet_ver'] = @options[:puppet]
57
+ config['CONFIG']['facter_ver'] = @options[:facter]
58
+ config['CONFIG']['hiera_ver'] = @options[:hiera]
59
+ config['CONFIG']['hiera_puppet_ver'] = @options[:hiera_puppet]
60
+ end
61
+ # need to load expect versions of PE binaries
62
+ config
63
+ end
64
+
65
+ def is_pe?
66
+ @is_pe ||= @options[:type] =~ /pe/ ? true : false
67
+ unless ENV['IS_PE'].nil?
68
+ @is_pe ||= ENV['IS_PE'] == 'true'
69
+ end
70
+ @is_pe
71
+ end
72
+
73
+ def puppet_enterprise_dir
74
+ @pe_dir ||= ENV['pe_dist_dir'] || '/opt/enterprise/dists'
75
+ end
76
+
77
+ def load_pe_version
78
+ dist_dir = puppet_enterprise_dir
79
+ version_file = ENV['pe_version_file'] || 'LATEST'
80
+ version = ""
81
+ begin
82
+ open("#{dist_dir}/#{version_file}") do |file|
83
+ while line = file.gets
84
+ if /(\w.*)/ =~ line then
85
+ version = $1.strip
86
+ @logger.debug "Found LATEST: Puppet Enterprise Version #{version}"
87
+ end
88
+ end
89
+ end
90
+ rescue
91
+ version = 'unknown'
92
+ end
93
+ return version
94
+ end
95
+
96
+ def puppet_enterprise_version
97
+ @pe_ver ||= load_pe_version if is_pe?
98
+ end
99
+
100
+ def load_pe_version_win
101
+ dist_dir = puppet_enterprise_dir
102
+ version_file = ENV['pe_version_file'] || 'LATEST-win'
103
+ version = ""
104
+ begin
105
+ open("#{dist_dir}/#{version_file}") do |file|
106
+ while line = file.gets
107
+ if /(\w.*)/ =~ line then
108
+ version=$1.strip
109
+ @logger.debug "Found LATEST: Puppet Enterprise Windows Version #{version}"
110
+ end
111
+ end
112
+ end
113
+ rescue
114
+ version = 'unknown'
115
+ end
116
+ return version
117
+ end
118
+
119
+ def puppet_enterprise_version_win
120
+ @pe_ver_win ||= load_pe_version_win if is_pe?
121
+ end
122
+
123
+ # Print out test configuration
124
+ def dump
125
+ # Access "platform" for each host
126
+ @config["HOSTS"].each_key do|host|
127
+ @logger.notify "Platform for #{host} #{@config["HOSTS"][host]['platform']}"
128
+ end
129
+
130
+ # Access "roles" for each host
131
+ @config["HOSTS"].each_key do|host|
132
+ @config["HOSTS"][host]['roles'].each do |role|
133
+ @logger.notify "Role for #{host} #{role}"
134
+ end
135
+ end
136
+
137
+ # Print out Ruby versions
138
+ @config["HOSTS"].each_key do|host|
139
+ @logger.notify "Ruby version for #{host} #{@config["HOSTS"][host][:ruby_ver]}"
140
+ end
141
+
142
+ # Access @config keys/values
143
+ @config["CONFIG"].each_key do|cfg|
144
+ @logger.notify "Config Key|Val: #{cfg} #{@config["CONFIG"][cfg].inspect}"
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,288 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rexml/document'
3
+ require 'fileutils'
4
+ %w(test_case logger).each do |lib|
5
+ begin
6
+ require "beaker/#{lib}"
7
+ rescue LoadError
8
+ require File.expand_path(File.join(File.dirname(__FILE__), lib))
9
+ end
10
+ end
11
+
12
+ module Beaker
13
+ # This Class is in need of some cleaning up beyond what can be quickly done.
14
+ # Things to keep in mind:
15
+ # * Global State Change
16
+ # * File Creation Relative to CWD -- Should be a config option
17
+ # * Better Method Documentation
18
+ class TestSuite
19
+ attr_reader :name, :options, :config, :fail_mode
20
+
21
+ def initialize(name, hosts, options, config, fail_mode = nil)
22
+ @name = name.gsub(/\s+/, '-')
23
+ @hosts = hosts
24
+ @run = false
25
+ @options = options
26
+ @config = config
27
+ @fail_mode = @options[:fail_mode] || fail_mode
28
+ @logger = options[:logger]
29
+
30
+ @test_cases = []
31
+ @test_files = []
32
+
33
+ options[:tests].each do |root|
34
+ if File.file? root then
35
+ @test_files << root
36
+ else
37
+ @test_files += Dir.glob(
38
+ File.join(root, "**/*.rb")
39
+ ).select { |f| File.file?(f) }
40
+ end
41
+ end
42
+ report_and_raise(@logger, RuntimeError.new("#{@name}: no test files found..."), "TestSuite: initialize") if @test_files.empty?
43
+
44
+ @test_files = @test_files.sort
45
+ rescue => e
46
+ report_and_raise(@logger, e, "TestSuite: initialize")
47
+ end
48
+
49
+ def run
50
+ @run = true
51
+ @start_time = Time.now
52
+
53
+ configure_logging
54
+
55
+ @test_files.each do |test_file|
56
+ @logger.notify
57
+ @logger.notify "Begin #{test_file}"
58
+ start = Time.now
59
+ test_case = TestCase.new(@hosts, @logger, config, options, test_file).run_test
60
+ duration = Time.now - start
61
+ @test_cases << test_case
62
+
63
+ state = test_case.test_status == :skip ? 'skipp' : test_case.test_status
64
+ msg = "#{test_file} #{state}ed in %.2f seconds" % duration.to_f
65
+ case test_case.test_status
66
+ when :pass
67
+ @logger.success msg
68
+ when :skip
69
+ @logger.debug msg
70
+ when :fail
71
+ @logger.error msg
72
+ break if fail_mode #all failure modes cause us to kick out early on failure
73
+ when :error
74
+ @logger.warn msg
75
+ break if fail_mode #all failure modes cause use to kick out early on error
76
+ end
77
+ end
78
+
79
+ # REVISIT: This changes global state, breaking logging in any future runs
80
+ # of the suite – or, at least, making them highly confusing for anyone who
81
+ # has not studied the implementation in detail. --daniel 2011-03-14
82
+ summarize
83
+ write_junit_xml if options[:xml]
84
+
85
+ # Allow chaining operations...
86
+ return self
87
+ end
88
+
89
+ def run_and_raise_on_failure
90
+ begin
91
+ run
92
+ return self if success?
93
+ rescue => e
94
+ #failed during run
95
+ report_and_raise(@logger, e, "TestSuite :run_and_raise_on_failure")
96
+ else
97
+ #failed during test
98
+ report_and_raise(@logger, RuntimeError.new("Failed while running the #{name} suite"), "TestSuite: report_and_raise_on_failure")
99
+ end
100
+ end
101
+
102
+ def fail_without_test_run
103
+ report_and_raise(@logger, RuntimeError.new("#{@name}: you have not run the tests yet"), "TestSuite: fail_without_test_run") unless @run
104
+ end
105
+
106
+ def success?
107
+ fail_without_test_run
108
+ sum_failed == 0
109
+ end
110
+
111
+ def failed?
112
+ !success?
113
+ end
114
+
115
+ def test_count
116
+ @test_count ||= @test_cases.length
117
+ end
118
+
119
+ def passed_tests
120
+ @passed_tests ||= @test_cases.select { |c| c.test_status == :pass }.length
121
+ end
122
+
123
+ def errored_tests
124
+ @errored_tests ||= @test_cases.select { |c| c.test_status == :error }.length
125
+ end
126
+
127
+ def failed_tests
128
+ @failed_tests ||= @test_cases.select { |c| c.test_status == :fail }.length
129
+ end
130
+
131
+ def skipped_tests
132
+ @skipped_tests ||= @test_cases.select { |c| c.test_status == :skip }.length
133
+ end
134
+
135
+ def pending_tests
136
+ @pending_tests ||= @test_cases.select {|c| c.test_status == :pending}.length
137
+ end
138
+
139
+ private
140
+
141
+ def sum_failed
142
+ @sum_failed ||= failed_tests + errored_tests
143
+ end
144
+
145
+ def write_junit_xml
146
+ # This should be a configuration option
147
+ File.directory?('junit') or FileUtils.mkdir('junit')
148
+
149
+ begin
150
+ doc = REXML::Document.new
151
+ doc.add(REXML::XMLDecl.new(1.0))
152
+
153
+ suite = REXML::Element.new('testsuite', doc)
154
+ suite.add_attribute('name', name)
155
+ suite.add_attribute('tests', test_count)
156
+ suite.add_attribute('errors', errored_tests)
157
+ suite.add_attribute('failures', failed_tests)
158
+ suite.add_attribute('skip', skipped_tests)
159
+ suite.add_attribute('pending', pending_tests)
160
+
161
+ @test_cases.each do |test|
162
+ item = REXML::Element.new('testcase', suite)
163
+ item.add_attribute('classname', File.dirname(test.path))
164
+ item.add_attribute('name', File.basename(test.path))
165
+ item.add_attribute('time', test.runtime)
166
+
167
+ # Did we fail? If so, report that.
168
+ # We need to remove the escape character from colorized text, the
169
+ # substitution of other entities is handled well by Rexml
170
+ if test.test_status == :fail || test.test_status == :error then
171
+ status = REXML::Element.new('failure', item)
172
+ status.add_attribute('type', test.test_status.to_s)
173
+ if test.exception then
174
+ status.add_attribute('message', test.exception.to_s.gsub(/\e/, ''))
175
+ status.text = test.exception.backtrace.join("\n")
176
+ end
177
+ end
178
+
179
+ if test.stdout then
180
+ REXML::Element.new('system-out', item).text =
181
+ test.stdout.gsub(/\e/, '')
182
+ end
183
+
184
+ if test.stderr then
185
+ text = REXML::Element.new('system-err', item)
186
+ text.text = test.stderr.gsub(/\e/, '')
187
+ end
188
+ end
189
+
190
+ # junit/name.xml will be created in a directory relative to the CWD
191
+ # -- JLS 2/12
192
+ File.open("junit/#{name}.xml", 'w') { |fh| doc.write(fh) }
193
+ rescue Exception => e
194
+ @logger.error "failure in XML output:\n#{e.to_s}\n" + e.backtrace.join("\n")
195
+ end
196
+ end
197
+
198
+ def summarize
199
+ fail_without_test_run
200
+
201
+ summary_logger = Logger.new(log_path("#{name}-summary.txt"), STDOUT)
202
+
203
+ summary_logger.notify <<-HEREDOC
204
+ Test Suite: #{name} @ #{@start_time}
205
+
206
+ - Host Configuration Summary -
207
+ HEREDOC
208
+
209
+ config.dump
210
+
211
+ elapsed_time = @test_cases.inject(0.0) {|r, t| r + t.runtime.to_f }
212
+ average_test_time = elapsed_time / test_count
213
+
214
+ summary_logger.notify %Q[
215
+
216
+ - Test Case Summary for suite '#{name}' -
217
+ Total Suite Time: %.2f seconds
218
+ Average Test Time: %.2f seconds
219
+ Attempted: #{test_count}
220
+ Passed: #{passed_tests}
221
+ Failed: #{failed_tests}
222
+ Errored: #{errored_tests}
223
+ Skipped: #{skipped_tests}
224
+ Pending: #{pending_tests}
225
+
226
+ - Specific Test Case Status -
227
+ ] % [elapsed_time, average_test_time]
228
+
229
+ grouped_summary = @test_cases.group_by{|test_case| test_case.test_status }
230
+
231
+ summary_logger.notify "Failed Tests Cases:"
232
+ (grouped_summary[:fail] || []).each do |test_case|
233
+ print_test_failure(test_case)
234
+ end
235
+
236
+ summary_logger.notify "Errored Tests Cases:"
237
+ (grouped_summary[:error] || []).each do |test_case|
238
+ print_test_failure(test_case)
239
+ end
240
+
241
+ summary_logger.notify "Skipped Tests Cases:"
242
+ (grouped_summary[:skip] || []).each do |test_case|
243
+ print_test_failure(test_case)
244
+ end
245
+
246
+ summary_logger.notify "Pending Tests Cases:"
247
+ (grouped_summary[:pending] || []).each do |test_case|
248
+ print_test_failure(test_case)
249
+ end
250
+
251
+ summary_logger.notify("\n\n")
252
+ end
253
+
254
+ def print_test_failure(test_case)
255
+ test_reported = if test_case.exception
256
+ "reported: #{test_case.exception.inspect}"
257
+ else
258
+ test_case.test_status
259
+ end
260
+ @logger.notify " Test Case #{test_case.path} #{test_reported}"
261
+ end
262
+
263
+ def log_path(name)
264
+ @@log_dir ||= File.join("log", @start_time.strftime("%F_%T"))
265
+ unless File.directory?(@@log_dir) then
266
+ FileUtils.mkdir_p(@@log_dir)
267
+ FileUtils.cp(options[:config],(File.join(@@log_dir,"config.yml")))
268
+
269
+ latest = File.join("log", "latest")
270
+ if !File.exist?(latest) or File.symlink?(latest) then
271
+ File.delete(latest) if File.exist?(latest)
272
+ File.symlink(File.basename(@@log_dir), latest)
273
+ end
274
+ end
275
+
276
+ File.join('log', 'latest', name)
277
+ end
278
+
279
+ # Setup log dir
280
+ def configure_logging
281
+ @logger.add_destination(log_path("#{@name}-run.log"))
282
+ #
283
+ # This is an awful hack to maintain backward compatibility until tests
284
+ # are ported to use logger.
285
+ Beaker.const_set(:Log, @logger) unless defined?( Log )
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,7 @@
1
+ [ 'ntp_control', 'setup_helper', 'repo_control', 'validator' ].each do |file|
2
+ begin
3
+ require "beaker/utils/#{file}"
4
+ rescue LoadError
5
+ require File.expand_path(File.join(File.dirname(__FILE__), 'utils', file))
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ module Beaker
2
+ module Utils
3
+ class NTPControl
4
+ NTPSERVER = 'pool.ntp.org'
5
+ def initialize(options, hosts)
6
+ @options = options.dup
7
+ @hosts = hosts
8
+ @logger = options[:logger]
9
+ end
10
+
11
+ def timesync
12
+ @logger.notify "Update system time sync"
13
+ @logger.notify "run ntpdate against NTP pool systems"
14
+ @hosts.each do |host|
15
+ success=FALSE
16
+ if host['platform'].include? 'solaris-10'
17
+ host.exec(Command.new("sleep 10 && ntpdate -w #{NTPSERVER}"))
18
+ elsif host['platform'].include? 'windows'
19
+ # The exit code of 5 is for Windows 2008 systems where the w32tm /register command
20
+ # is not actually necessary.
21
+ host.exec(Command.new("w32tm /register"), :acceptable_exit_codes => [0,5])
22
+ host.exec(Command.new("net start w32time"), :acceptable_exit_codes => [0,2])
23
+ host.exec(Command.new("w32tm /config /manualpeerlist:#{NTPSERVER} /syncfromflags:manual /update"))
24
+ host.exec(Command.new("w32tm /resync"))
25
+ else
26
+ count=0
27
+ until success do
28
+ count+=1
29
+ raise "ntp time sync failed after #{count} tries" and break if count > 3
30
+ if host.exec(Command.new("ntpdate -t 20 #{NTPSERVER}")).exit_code == 0
31
+ success=TRUE
32
+ end
33
+ end
34
+ @logger.notify "NTP date succeeded after #{count} tries"
35
+ end
36
+ end
37
+ rescue => e
38
+ report_and_raise(@logger, e, "timesync (--ntp)")
39
+ end
40
+ end
41
+ end
42
+ end