beaker 2.3.0 → 2.4.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.
- checksums.yaml +8 -8
- data/HISTORY.md +366 -2
- data/ext/completion/beaker-completion.bash +1 -1
- data/lib/beaker.rb +1 -1
- data/lib/beaker/answers.rb +3 -1
- data/lib/beaker/answers/version40.rb +42 -0
- data/lib/beaker/cli.rb +0 -2
- data/lib/beaker/command.rb +10 -2
- data/lib/beaker/dsl/ezbake_utils.rb +195 -157
- data/lib/beaker/dsl/helpers.rb +9 -6
- data/lib/beaker/dsl/install_utils.rb +22 -8
- data/lib/beaker/dsl/structure.rb +67 -0
- data/lib/beaker/host.rb +14 -4
- data/lib/beaker/host/mac.rb +4 -0
- data/lib/beaker/host/pswindows.rb +79 -0
- data/lib/beaker/host/pswindows/exec.rb +29 -0
- data/lib/beaker/host/pswindows/file.rb +15 -0
- data/lib/beaker/host/pswindows/group.rb +36 -0
- data/lib/beaker/host/pswindows/pkg.rb +47 -0
- data/lib/beaker/host/pswindows/user.rb +32 -0
- data/lib/beaker/host/unix.rb +16 -6
- data/lib/beaker/host/windows.rb +6 -2
- data/lib/beaker/host_prebuilt_steps.rb +2 -0
- data/lib/beaker/hypervisor.rb +3 -1
- data/lib/beaker/hypervisor/aws_sdk.rb +6 -1
- data/lib/beaker/hypervisor/docker.rb +6 -1
- data/lib/beaker/hypervisor/vagrant.rb +1 -1
- data/lib/beaker/hypervisor/vagrant_parallels.rb +18 -0
- data/lib/beaker/logger.rb +8 -1
- data/lib/beaker/logger_junit.rb +157 -0
- data/lib/beaker/network_manager.rb +28 -0
- data/lib/beaker/options/presets.rb +6 -0
- data/lib/beaker/test_suite.rb +65 -136
- data/lib/beaker/version.rb +1 -1
- data/spec/beaker/answers_spec.rb +74 -0
- data/spec/beaker/dsl/ezbake_utils_spec.rb +167 -126
- data/spec/beaker/dsl/install_utils_spec.rb +5 -4
- data/spec/beaker/dsl/structure_spec.rb +28 -1
- data/spec/beaker/host_prebuilt_steps_spec.rb +2 -1
- data/spec/beaker/host_spec.rb +1 -7
- data/spec/beaker/hypervisor/docker_spec.rb +19 -1
- data/spec/beaker/hypervisor/vagrant_parallels_spec.rb +44 -0
- data/spec/beaker/logger_junit_spec.rb +93 -0
- data/spec/beaker/network_manager_spec.rb +52 -0
- metadata +14 -2
data/lib/beaker/host/windows.rb
CHANGED
@@ -21,6 +21,8 @@ module Windows
|
|
21
21
|
'group' => 'Administrators',
|
22
22
|
'puppetservice' => 'pe-httpd',
|
23
23
|
'puppetpath' => '`cygpath -smF 35`/PuppetLabs/puppet/etc',
|
24
|
+
'puppetconfdir' => '`cygpath -smF 35`/PuppetLabs/puppet/etc',
|
25
|
+
'puppetcodedir' => '`cygpath -smF 35`/PuppetLabs/puppet/etc',
|
24
26
|
'hieraconf' => '`cygpath -smF 35`/Puppetlabs/puppet/etc/hiera.yaml',
|
25
27
|
'puppetvardir' => '`cygpath -smF 35`/PuppetLabs/puppet/var',
|
26
28
|
'distmoduledir' => '`cygpath -smF 35`/PuppetLabs/puppet/etc/modules',
|
@@ -37,14 +39,16 @@ module Windows
|
|
37
39
|
'user' => 'Administrator',
|
38
40
|
'group' => 'Administrators',
|
39
41
|
'puppetpath' => '`cygpath -smF 35`/PuppetLabs/puppet/etc',
|
42
|
+
'puppetconfdir' => '`cygpath -smF 35`/PuppetLabs/puppet/etc',
|
43
|
+
'puppetcodedir' => '`cygpath -smF 35`/PuppetLabs/puppet/etc',
|
40
44
|
'hieraconf' => '`cygpath -smF 35`/Puppetlabs/puppet/etc/hiera.yaml',
|
41
45
|
'puppetvardir' => '`cygpath -smF 35`/PuppetLabs/puppet/var',
|
42
46
|
'distmoduledir' => '`cygpath -smF 35`/PuppetLabs/puppet/etc/modules',
|
43
47
|
'sitemoduledir' => 'C:/usr/share/puppet/modules',
|
44
48
|
'hieralibdir' => '`cygpath -w /opt/puppet-git-repos/hiera/lib`',
|
45
49
|
'hierapuppetlibdir' => '`cygpath -w /opt/puppet-git-repos/hiera-puppet/lib`',
|
46
|
-
#let's just add both potential bin dirs to the path
|
47
|
-
'puppetbindir' => '/cygdrive/c/Program Files (x86)/Puppet Labs/Puppet/bin:/cygdrive/c/Program Files/Puppet Labs/Puppet/bin',
|
50
|
+
#let's just add both potential bin dirs to the path, include ruby too for `gem`, `ruby`, etc
|
51
|
+
'puppetbindir' => '/cygdrive/c/Program Files (x86)/Puppet Labs/Puppet/bin:/cygdrive/c/Program Files/Puppet Labs/Puppet/bin:/cygdrive/c/Program Files (x86)/Puppet Labs/Puppet/sys/ruby/bin:/cygdrive/c/Program Files/Puppet Labs/Puppet/sys/ruby/bin',
|
48
52
|
'hierabindir' => '/opt/puppet-git-repos/hiera/bin',
|
49
53
|
'pathseparator' => ';',
|
50
54
|
})
|
data/lib/beaker/hypervisor.rb
CHANGED
@@ -46,6 +46,8 @@ module Beaker
|
|
46
46
|
Beaker::VagrantFusion
|
47
47
|
when /^vagrant_workstation$/
|
48
48
|
Beaker::VagrantWorkstation
|
49
|
+
when /^vagrant_parallels$/
|
50
|
+
Beaker::VagrantParallels
|
49
51
|
when /^google$/
|
50
52
|
Beaker::GoogleCompute
|
51
53
|
when /^docker$/
|
@@ -127,6 +129,6 @@ module Beaker
|
|
127
129
|
end
|
128
130
|
end
|
129
131
|
|
130
|
-
[ 'vsphere_helper', 'vagrant', 'vagrant_virtualbox', 'vagrant_libvirt', 'vagrant_fusion', 'vagrant_workstation', 'fusion', 'aws_sdk', 'vsphere', 'vcloud', 'vcloud_pooled', 'aixer', 'solaris', 'docker', 'google_compute', 'openstack' ].each do |lib|
|
132
|
+
[ 'vsphere_helper', 'vagrant', 'vagrant_virtualbox', 'vagrant_parallels', 'vagrant_libvirt', 'vagrant_fusion', 'vagrant_workstation', 'fusion', 'aws_sdk', 'vsphere', 'vcloud', 'vcloud_pooled', 'aixer', 'solaris', 'docker', 'google_compute', 'openstack' ].each do |lib|
|
131
133
|
require "beaker/hypervisor/#{lib}"
|
132
134
|
end
|
@@ -452,7 +452,12 @@ module Beaker
|
|
452
452
|
# @api private
|
453
453
|
def set_hostnames
|
454
454
|
@hosts.each do |host|
|
455
|
-
host
|
455
|
+
if host['platform'] =~ /el-7/
|
456
|
+
# on el-7 hosts, the hostname command doesn't "stick" randomly
|
457
|
+
host.exec(Command.new("hostnamectl set-hostname #{host.name}"))
|
458
|
+
else
|
459
|
+
host.exec(Command.new("hostname #{host.name}"))
|
460
|
+
end
|
456
461
|
end
|
457
462
|
end
|
458
463
|
|
@@ -11,7 +11,12 @@ module Beaker
|
|
11
11
|
::Docker.options = { :write_timeout => 300, :read_timeout => 300 }.merge(::Docker.options || {})
|
12
12
|
# assert that the docker-api gem can talk to your docker
|
13
13
|
# enpoint. Will raise if there is a version mismatch
|
14
|
-
|
14
|
+
begin
|
15
|
+
::Docker.validate_version!
|
16
|
+
rescue Excon::Errors::SocketError => e
|
17
|
+
raise "Docker instance not found.\nif you are on OSX, you might not have Boot2Docker setup correctly\nCheck your DOCKER_HOST variable has been set"
|
18
|
+
end
|
19
|
+
|
15
20
|
# Pass on all the logging from docker-api to the beaker logger instance
|
16
21
|
::Docker.logger = @logger
|
17
22
|
end
|
@@ -37,7 +37,7 @@ module Beaker
|
|
37
37
|
if /windows/i.match(host['platform'])
|
38
38
|
v_file << " v.vm.network :forwarded_port, guest: 3389, host: 3389\n"
|
39
39
|
v_file << " v.vm.network :forwarded_port, guest: 5985, host: 5985, id: 'winrm', auto_correct: true\n"
|
40
|
-
v_file << " v.vm.guest = :windows"
|
40
|
+
v_file << " v.vm.guest = :windows\n"
|
41
41
|
end
|
42
42
|
|
43
43
|
if /osx/i.match(host['platform'])
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'beaker/hypervisor/vagrant'
|
2
|
+
|
3
|
+
class Beaker::VagrantParallels < Beaker::Vagrant
|
4
|
+
def provision(provider = 'parallels')
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.provider_vfile_section(host, options)
|
9
|
+
provider_section = ""
|
10
|
+
provider_section << " v.vm.provider :parallels do |prl|\n"
|
11
|
+
provider_section << " prl.optimize_power_consumption = false\n"
|
12
|
+
provider_section << " prl.memory = '#{options['vagrant_memsize'] ||= '1024'}'\n"
|
13
|
+
provider_section << " prl.update_guest_tools = false\n" if options[:prl_update_guest_tools] == 'disable'
|
14
|
+
provider_section << " end\n"
|
15
|
+
|
16
|
+
provider_section
|
17
|
+
end
|
18
|
+
end
|
data/lib/beaker/logger.rb
CHANGED
@@ -257,7 +257,7 @@ module Beaker
|
|
257
257
|
# @return [Array<String>] An array of strings that do not have color codes
|
258
258
|
def strip_colors_from lines
|
259
259
|
Array( lines ).map do |line|
|
260
|
-
convert(line)
|
260
|
+
Logger.strip_color_codes(convert(line))
|
261
261
|
end
|
262
262
|
end
|
263
263
|
|
@@ -312,6 +312,13 @@ module Beaker
|
|
312
312
|
log_dir
|
313
313
|
end
|
314
314
|
|
315
|
+
#Remove color codes from provided string. Color codes are of the format /(\e\[\d\d;\d\dm)+/.
|
316
|
+
#@param [String] text The string to remove color codes from
|
317
|
+
#@return [String] The text without color codes
|
318
|
+
def Logger.strip_color_codes(text)
|
319
|
+
text.gsub(/(\e|\^\[)\[(\d*;)*\d*m/, '')
|
320
|
+
end
|
321
|
+
|
315
322
|
private
|
316
323
|
# Expand each symlink found to its full path
|
317
324
|
# Lines are assumed to be in the format "String : Integer"
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Beaker
|
2
|
+
# The Beaker JUnit Logger class
|
3
|
+
# This module handles message reporting from Beaker to the JUnit format
|
4
|
+
#
|
5
|
+
# There is a specific pattern for using this class.
|
6
|
+
# Here's a list of example usages:
|
7
|
+
# - {Beaker::TestSuite::TestSuiteResult#write_junit_xml}
|
8
|
+
module LoggerJunit
|
9
|
+
|
10
|
+
# writes the xml created in the block to the xml file given
|
11
|
+
#
|
12
|
+
# Note: Error Recovery should take place in the caller of this
|
13
|
+
# method in order to recover gracefully
|
14
|
+
#
|
15
|
+
# @param [String] xml_file Path to the xml file
|
16
|
+
# @param [String] stylesheet Path to the stylesheet file
|
17
|
+
# @param [Proc] block XML message construction block
|
18
|
+
#
|
19
|
+
# @return nil
|
20
|
+
def self.write_xml(xml_file, stylesheet, &block)
|
21
|
+
doc, suites = self.get_xml_contents(xml_file, name, stylesheet)
|
22
|
+
|
23
|
+
if block_given?
|
24
|
+
case block.arity
|
25
|
+
when 2
|
26
|
+
yield doc, suites
|
27
|
+
else
|
28
|
+
raise ArgumentError.new "write_xml block takes 2 arguments, not #{block.arity}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
self.finish(doc, xml_file)
|
33
|
+
end
|
34
|
+
|
35
|
+
# writes out xml content for a doc
|
36
|
+
#
|
37
|
+
# @param [Nokogiri::XML] doc Nokogiri doc containing content to write
|
38
|
+
# @param [String] xml_file Path to the xml file to write
|
39
|
+
#
|
40
|
+
# @return nil
|
41
|
+
def self.finish(doc, xml_file)
|
42
|
+
# junit/name.xml will be created in a directory relative to the CWD
|
43
|
+
# -- JLS 2/12
|
44
|
+
File.open(xml_file, 'w') { |fh| fh.write(doc.to_xml) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# gets the xml doc & suites in order to build your xml output on top of
|
48
|
+
#
|
49
|
+
# @param [String] xml_file Path to the xml file
|
50
|
+
# @param [String] name Name of the testsuite you're writing
|
51
|
+
# @param [String] stylesheet Path to the stylesheet file
|
52
|
+
#
|
53
|
+
# @return [Nokogiri::XML] doc to use for your xml content
|
54
|
+
# @return [Nokogiri::XML::Node] suites to add your content to
|
55
|
+
def self.get_xml_contents(xml_file, name, stylesheet)
|
56
|
+
self.copy_stylesheet_into_xml_dir(stylesheet, xml_file)
|
57
|
+
xml_file_already_exists = File.file?(xml_file)
|
58
|
+
doc = self.get_doc_for_filename(xml_file, stylesheet, xml_file_already_exists)
|
59
|
+
suites = self.get_testsuites_from_doc(doc, name, xml_file_already_exists)
|
60
|
+
return doc, suites
|
61
|
+
end
|
62
|
+
|
63
|
+
# copies given stylesheet into the directory of the xml file given
|
64
|
+
#
|
65
|
+
# @param [String] stylesheet Path to the stylesheet file
|
66
|
+
# @param [String] xml_file Path to the xml file
|
67
|
+
#
|
68
|
+
# @return nil
|
69
|
+
def self.copy_stylesheet_into_xml_dir(stylesheet, xml_file)
|
70
|
+
if not File.file?(File.join(File.dirname(xml_file), File.basename(stylesheet)))
|
71
|
+
FileUtils.copy(stylesheet, File.join(File.dirname(xml_file), File.basename(stylesheet)))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# sets up doc & gives us the suites for the testsuite named
|
76
|
+
#
|
77
|
+
# @param [Nokogiri::XML] doc Doc that you're getting suites from
|
78
|
+
# @param [String] name Testsuite node name
|
79
|
+
# @param [Boolean] already_existed Whether or not the doc already existed
|
80
|
+
#
|
81
|
+
# @return [Nokogiri::XML::Node] testsuites
|
82
|
+
def self.get_testsuites_from_doc(doc, name, already_existed)
|
83
|
+
#check to see if an output file already exists, if it does add or replace test suite data
|
84
|
+
if already_existed
|
85
|
+
suites = doc.at_xpath('testsuites')
|
86
|
+
#remove old data
|
87
|
+
doc.search("//testsuite").each do |node|
|
88
|
+
if node['name'] =~ /#{name}/
|
89
|
+
node.unlink
|
90
|
+
end
|
91
|
+
end
|
92
|
+
else
|
93
|
+
suites = Nokogiri::XML::Node.new('testsuites', doc)
|
94
|
+
suites.parent = doc
|
95
|
+
end
|
96
|
+
return suites
|
97
|
+
end
|
98
|
+
|
99
|
+
# gives the document object for a particular file
|
100
|
+
#
|
101
|
+
# @param [String] filename Path to the file that you're opening
|
102
|
+
# @param [String] stylesheet Path to the stylesheet for this doc
|
103
|
+
# @param [Boolean] already_exists Whether or not the file already exists
|
104
|
+
#
|
105
|
+
# @return [Nokogiri::XML] Doc that you want to write in
|
106
|
+
def self.get_doc_for_filename(filename, stylesheet, already_exists)
|
107
|
+
if already_exists
|
108
|
+
doc = Nokogiri::XML(File.open(filename, 'r'))
|
109
|
+
else
|
110
|
+
#no existing file, create a new one
|
111
|
+
doc = Nokogiri::XML::Document.new()
|
112
|
+
doc.encoding = 'UTF-8'
|
113
|
+
pi = Nokogiri::XML::ProcessingInstruction.new(doc, "xml-stylesheet", "type=\"text/xsl\" href=\"#{File.basename(stylesheet)}\"")
|
114
|
+
pi.parent = doc
|
115
|
+
end
|
116
|
+
return doc
|
117
|
+
end
|
118
|
+
|
119
|
+
# Remove color codes and invalid XML characters from provided string
|
120
|
+
# @param [String] string The string to format
|
121
|
+
# @return [String] the correctly formatted cdata
|
122
|
+
def self.format_cdata string
|
123
|
+
self.escape_invalid_xml_chars(Logger.strip_color_codes(string))
|
124
|
+
end
|
125
|
+
|
126
|
+
# Escape invalid XML UTF-8 codes from provided string, see http://www.w3.org/TR/xml/#charsets for valid
|
127
|
+
# character specification
|
128
|
+
# @param [String] string The string to remove invalid codes from
|
129
|
+
# @return [String] Properly escaped string
|
130
|
+
def self.escape_invalid_xml_chars string
|
131
|
+
escaped_string = ""
|
132
|
+
string.chars.each do |i|
|
133
|
+
char_as_codestring = i.unpack("U*").join
|
134
|
+
if self.is_valid_xml(char_as_codestring.to_i)
|
135
|
+
escaped_string << i
|
136
|
+
else
|
137
|
+
escaped_string << "\\#{char_as_codestring}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
escaped_string
|
141
|
+
end
|
142
|
+
|
143
|
+
# Determine if the provided number falls in the range of accepted xml unicode values
|
144
|
+
# See http://www.w3.org/TR/xml/#charsets for valid for valid character specifications.
|
145
|
+
# @param [Integer] int The number to check against
|
146
|
+
# @return [Boolean] True, if the number corresponds to a valid xml unicode character, otherwise false
|
147
|
+
def self.is_valid_xml(int)
|
148
|
+
return ( int == 0x9 or
|
149
|
+
int == 0xA or
|
150
|
+
( int >= 0x0020 and int <= 0xD7FF ) or
|
151
|
+
( int >= 0xE000 and int <= 0xFFFD ) or
|
152
|
+
( int >= 0x100000 and int <= 0x10FFFF )
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -25,6 +25,11 @@ module Beaker
|
|
25
25
|
@hosts = []
|
26
26
|
@machines = {}
|
27
27
|
@hypervisors = nil
|
28
|
+
|
29
|
+
@options[:timestamp] = Time.now unless @options.has_key?(:timestamp)
|
30
|
+
@options[:xml_dated_dir] = Beaker::Logger.generate_dated_log_folder(@options[:xml_dir], @options[:timestamp])
|
31
|
+
@options[:log_dated_dir] = Beaker::Logger.generate_dated_log_folder(@options[:log_dir], @options[:timestamp])
|
32
|
+
@options[:logger_sut] = Beaker::Logger.new(File.join(@options[:log_dated_dir], @options[:log_sut_event]), { :quiet => true })
|
28
33
|
end
|
29
34
|
|
30
35
|
#Provision all virtual machines. Provision machines according to their set hypervisor, if no hypervisor
|
@@ -47,6 +52,9 @@ module Beaker
|
|
47
52
|
@machines.each_key do |type|
|
48
53
|
@hypervisors[type] = Beaker::Hypervisor.create(type, @machines[type], @options)
|
49
54
|
@hosts << @machines[type]
|
55
|
+
@machines[type].each do |host|
|
56
|
+
log_sut_event host, true
|
57
|
+
end
|
50
58
|
end
|
51
59
|
@hosts = @hosts.flatten
|
52
60
|
@hosts
|
@@ -91,10 +99,30 @@ module Beaker
|
|
91
99
|
if @hypervisors
|
92
100
|
@hypervisors.each_key do |type|
|
93
101
|
@hypervisors[type].cleanup
|
102
|
+
@hypervisors[type].instance_variable_get(:@hosts).each do |host|
|
103
|
+
log_sut_event host, false
|
104
|
+
end
|
94
105
|
end
|
95
106
|
end
|
96
107
|
@hypervisors = nil
|
97
108
|
end
|
98
109
|
|
110
|
+
# logs provisioning events
|
111
|
+
#
|
112
|
+
# @param [Host] host The host that the event is happening to
|
113
|
+
# @param [Boolean] create Whether the event is creation or cleaning up
|
114
|
+
#
|
115
|
+
# @return [String] the log line created for this event
|
116
|
+
def log_sut_event host, create
|
117
|
+
raise ArgumentError.new "log_sut_event called before sut logger created. skipping #{host}, #{create}" unless @options.has_key?(:logger_sut)
|
118
|
+
sut_logger = @options[:logger_sut]
|
119
|
+
time = Time.new
|
120
|
+
stamp = time.strftime('%Y-%m-%d %H:%M:%S')
|
121
|
+
verb = create ? '+' : '-'
|
122
|
+
line = "#{stamp}\t[#{verb}]\t#{host['hypervisor']}\t#{host['platform']}\t#{host}"
|
123
|
+
sut_logger.notify line
|
124
|
+
line
|
125
|
+
end
|
126
|
+
|
99
127
|
end
|
100
128
|
end
|
@@ -113,6 +113,10 @@ module Beaker
|
|
113
113
|
:project => 'Beaker',
|
114
114
|
:department => 'unknown',
|
115
115
|
:created_by => ENV['USER'] || ENV['USERNAME'] || 'unknown',
|
116
|
+
:openstack_api_key => ENV['OS_PASSWORD'],
|
117
|
+
:openstack_username => ENV['OS_USERNAME'],
|
118
|
+
:openstack_auth_url => "#{ENV['OS_AUTH_URL']}/tokens",
|
119
|
+
:openstack_tenant => ENV['OS_TENANT_NAME'],
|
116
120
|
:jenkins_build_url => nil,
|
117
121
|
:validate => true,
|
118
122
|
:configure => true,
|
@@ -131,6 +135,7 @@ module Beaker
|
|
131
135
|
:xml_file => 'beaker_junit.xml',
|
132
136
|
:xml_stylesheet => 'junit.xsl',
|
133
137
|
:log_dir => 'log',
|
138
|
+
:log_sut_event => 'sut.log',
|
134
139
|
:color => true,
|
135
140
|
:dry_run => false,
|
136
141
|
:timeout => 300,
|
@@ -182,6 +187,7 @@ module Beaker
|
|
182
187
|
:q_rbac_database_user => 'RbhNBklm',
|
183
188
|
:q_rbac_database_name => 'pe-rbac',
|
184
189
|
:q_rbac_database_password => '~!@#$%^*-/ aZ',
|
190
|
+
:q_install_update_server => 'y',
|
185
191
|
},
|
186
192
|
:dot_fog => File.join(ENV['HOME'], '.fog'),
|
187
193
|
:ec2_yaml => 'config/image_templates/ec2.yaml',
|
data/lib/beaker/test_suite.rb
CHANGED
@@ -148,156 +148,85 @@ module Beaker
|
|
148
148
|
@logger.notify " Test Case #{test_case.path} #{test_reported}"
|
149
149
|
end
|
150
150
|
|
151
|
-
|
152
|
-
|
153
|
-
#@return [String] The text without color codes
|
154
|
-
def strip_color_codes(text)
|
155
|
-
text.gsub(/(\e|\^\[)\[(\d*;)*\d*m/, '')
|
156
|
-
end
|
157
|
-
|
158
|
-
# Determine if the provided number falls in the range of accepted xml unicode values
|
159
|
-
# See http://www.w3.org/TR/xml/#charsets for valid for valid character specifications.
|
160
|
-
# @param [Integer] int The number to check against
|
161
|
-
# @return [Boolean] True, if the number corresponds to a valid xml unicode character, otherwise false
|
162
|
-
def is_valid_xml(int)
|
163
|
-
return ( int == 0x9 or
|
164
|
-
int == 0xA or
|
165
|
-
( int >= 0x0020 and int <= 0xD7FF ) or
|
166
|
-
( int >= 0xE000 and int <= 0xFFFD ) or
|
167
|
-
( int >= 0x100000 and int <= 0x10FFFF )
|
168
|
-
)
|
169
|
-
end
|
170
|
-
|
171
|
-
# Escape invalid XML UTF-8 codes from provided string, see http://www.w3.org/TR/xml/#charsets for valid
|
172
|
-
# character specification
|
173
|
-
# @param [String] string The string to remove invalid codes from
|
174
|
-
def escape_invalid_xml_chars string
|
175
|
-
escaped_string = ""
|
176
|
-
string.chars.each do |i|
|
177
|
-
char_as_codestring = i.unpack("U*").join
|
178
|
-
if is_valid_xml(char_as_codestring.to_i)
|
179
|
-
escaped_string << i
|
180
|
-
else
|
181
|
-
escaped_string << "\\#{char_as_codestring}"
|
182
|
-
end
|
183
|
-
end
|
184
|
-
escaped_string
|
185
|
-
end
|
151
|
+
def write_junit_xml(xml_file)
|
152
|
+
stylesheet = File.join(@options[:project_root], @options[:xml_stylesheet])
|
186
153
|
|
187
|
-
# Remove color codes and invalid XML characters from provided string
|
188
|
-
# @param [String] string The string to format
|
189
|
-
def format_cdata string
|
190
|
-
escape_invalid_xml_chars(strip_color_codes(string))
|
191
|
-
end
|
192
|
-
|
193
|
-
#Format and print the {TestSuiteResult} as JUnit XML
|
194
|
-
#@param [String] xml_file The full path to print the output to.
|
195
|
-
#@param [String] stylesheet The full path to a JUnit XML stylesheet
|
196
|
-
def write_junit_xml(xml_file, stylesheet)
|
197
154
|
begin
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
155
|
+
LoggerJunit.write_xml(xml_file, stylesheet) do |doc, suites|
|
156
|
+
|
157
|
+
suite = Nokogiri::XML::Node.new('testsuite', doc)
|
158
|
+
suite['name'] = @name
|
159
|
+
suite['tests'] = test_count
|
160
|
+
suite['errors'] = errored_tests
|
161
|
+
suite['failures'] = failed_tests
|
162
|
+
suite['skip'] = skipped_tests
|
163
|
+
suite['pending'] = pending_tests
|
164
|
+
suite['total'] = @total_tests
|
165
|
+
suite['time'] = "%f" % (stop_time - start_time)
|
166
|
+
properties = Nokogiri::XML::Node.new('properties', doc)
|
167
|
+
@options.each_pair do | name, value |
|
168
|
+
property = Nokogiri::XML::Node.new('property', doc)
|
169
|
+
property['name'] = name
|
170
|
+
property['value'] = value
|
171
|
+
properties.add_child(property)
|
213
172
|
end
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
173
|
+
suite.add_child(properties)
|
174
|
+
|
175
|
+
@test_cases.each do |test|
|
176
|
+
item = Nokogiri::XML::Node.new('testcase', doc)
|
177
|
+
item['classname'] = File.dirname(test.path)
|
178
|
+
item['name'] = File.basename(test.path)
|
179
|
+
item['time'] = "%f" % test.runtime
|
180
|
+
|
181
|
+
# Did we fail? If so, report that.
|
182
|
+
# We need to remove the escape character from colorized text, the
|
183
|
+
# substitution of other entities is handled well by Rexml
|
184
|
+
if test.test_status == :fail || test.test_status == :error then
|
185
|
+
status = Nokogiri::XML::Node.new('failure', doc)
|
186
|
+
status['type'] = test.test_status.to_s
|
187
|
+
if test.exception then
|
188
|
+
status['message'] = test.exception.to_s.gsub(/\e/, '')
|
189
|
+
data = LoggerJunit.format_cdata(test.exception.backtrace.join('\n'))
|
190
|
+
status.add_child(status.document.create_cdata(data))
|
191
|
+
end
|
192
|
+
item.add_child(status)
|
193
|
+
end
|
223
194
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
suite['failures'] = failed_tests
|
229
|
-
suite['skip'] = skipped_tests
|
230
|
-
suite['pending'] = pending_tests
|
231
|
-
suite['total'] = @total_tests
|
232
|
-
suite['time'] = "%f" % (stop_time - start_time)
|
233
|
-
properties = Nokogiri::XML::Node.new('properties', doc)
|
234
|
-
@options.each_pair do | name, value |
|
235
|
-
property = Nokogiri::XML::Node.new('property', doc)
|
236
|
-
property['name'] = name
|
237
|
-
property['value'] = value
|
238
|
-
properties.add_child(property)
|
239
|
-
end
|
240
|
-
suite.add_child(properties)
|
241
|
-
|
242
|
-
@test_cases.each do |test|
|
243
|
-
item = Nokogiri::XML::Node.new('testcase', doc)
|
244
|
-
item['classname'] = File.dirname(test.path)
|
245
|
-
item['name'] = File.basename(test.path)
|
246
|
-
item['time'] = "%f" % test.runtime
|
247
|
-
|
248
|
-
# Did we fail? If so, report that.
|
249
|
-
# We need to remove the escape character from colorized text, the
|
250
|
-
# substitution of other entities is handled well by Rexml
|
251
|
-
if test.test_status == :fail || test.test_status == :error then
|
252
|
-
status = Nokogiri::XML::Node.new('failure', doc)
|
253
|
-
status['type'] = test.test_status.to_s
|
254
|
-
if test.exception then
|
255
|
-
status['message'] = test.exception.to_s.gsub(/\e/, '')
|
256
|
-
data = format_cdata(test.exception.backtrace.join('\n'))
|
257
|
-
status.add_child(status.document.create_cdata(data))
|
195
|
+
if test.test_status == :skip
|
196
|
+
status = Nokogiri::XML::Node.new('skip', doc)
|
197
|
+
status['type'] = test.test_status.to_s
|
198
|
+
item.add_child(status)
|
258
199
|
end
|
259
|
-
item.add_child(status)
|
260
|
-
end
|
261
200
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
201
|
+
if test.test_status == :pending
|
202
|
+
status = Nokogiri::XML::Node.new('pending', doc)
|
203
|
+
status['type'] = test.test_status.to_s
|
204
|
+
item.add_child(status)
|
205
|
+
end
|
267
206
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
207
|
+
if test.sublog then
|
208
|
+
stdout = Nokogiri::XML::Node.new('system-out', doc)
|
209
|
+
data = LoggerJunit.format_cdata(test.sublog)
|
210
|
+
stdout.add_child(stdout.document.create_cdata(data))
|
211
|
+
item.add_child(stdout)
|
212
|
+
end
|
273
213
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
214
|
+
if test.last_result and test.last_result.stderr and not test.last_result.stderr.empty? then
|
215
|
+
stderr = Nokogiri::XML::Node.new('system-err', doc)
|
216
|
+
data = LoggerJunit.format_cdata(test.last_result.stderr)
|
217
|
+
stderr.add_child(stderr.document.create_cdata(data))
|
218
|
+
item.add_child(stderr)
|
219
|
+
end
|
280
220
|
|
281
|
-
|
282
|
-
stderr = Nokogiri::XML::Node.new('system-err', doc)
|
283
|
-
data = format_cdata(test.last_result.stderr)
|
284
|
-
stderr.add_child(stderr.document.create_cdata(data))
|
285
|
-
item.add_child(stderr)
|
221
|
+
suite.add_child(item)
|
286
222
|
end
|
287
|
-
|
288
|
-
suite.add_child(item)
|
223
|
+
suites.add_child(suite)
|
289
224
|
end
|
290
|
-
suites.add_child(suite)
|
291
|
-
|
292
|
-
# junit/name.xml will be created in a directory relative to the CWD
|
293
|
-
# -- JLS 2/12
|
294
|
-
File.open(xml_file, 'w') { |fh| fh.write(doc.to_xml) }
|
295
|
-
|
296
225
|
rescue Exception => e
|
297
226
|
@logger.error "failure in XML output:\n#{e.to_s}\n" + e.backtrace.join("\n")
|
298
227
|
end
|
299
|
-
end
|
300
228
|
|
229
|
+
end
|
301
230
|
end
|
302
231
|
|
303
232
|
attr_reader :name, :options, :fail_mode
|
@@ -380,7 +309,7 @@ module Beaker
|
|
380
309
|
# of the suite – or, at least, making them highly confusing for anyone who
|
381
310
|
# has not studied the implementation in detail. --daniel 2011-03-14
|
382
311
|
@test_suite_results.summarize( Logger.new(log_path("#{name}-summary.txt", @options[:log_dated_dir]), STDOUT) )
|
383
|
-
@test_suite_results.write_junit_xml( log_path(@options[:xml_file], @options[:xml_dated_dir])
|
312
|
+
@test_suite_results.write_junit_xml( log_path(@options[:xml_file], @options[:xml_dated_dir]) )
|
384
313
|
|
385
314
|
#All done with this run, remove run log
|
386
315
|
@logger.remove_destination(run_log)
|