beaker 0.0.0

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.
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