beaker 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|