smart_monkey 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +77 -0
- data/Rakefile +57 -0
- data/Troubleshooting.md +61 -0
- data/VERSION +1 -0
- data/bin/smart_monkey +53 -0
- data/lib/bootstrap/css/bootstrap-responsive.css +1109 -0
- data/lib/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/lib/bootstrap/css/bootstrap.css +6167 -0
- data/lib/bootstrap/css/bootstrap.min.css +9 -0
- data/lib/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/lib/bootstrap/img/glyphicons-halflings.png +0 -0
- data/lib/bootstrap/js/bootstrap.js +2280 -0
- data/lib/bootstrap/js/bootstrap.min.js +6 -0
- data/lib/ios_device_log/deviceconsole +0 -0
- data/lib/smart_monkey.rb +2 -0
- data/lib/smart_monkey/command_helper.rb +71 -0
- data/lib/smart_monkey/monkey_runner.rb +549 -0
- data/lib/smart_monkey/templates/automation_result.xsl +61 -0
- data/lib/smart_monkey/templates/index.html.erb +77 -0
- data/lib/smart_monkey/templates/result.html.erb +110 -0
- data/lib/smart_monkey/templates/result_view.coffee +160 -0
- data/lib/smart_monkey/templates/result_view.js +250 -0
- data/lib/ui-auto-monkey/UIAutoMonkey.js +470 -0
- data/lib/ui-auto-monkey/custom.js +73 -0
- data/lib/ui-auto-monkey/handler/buttonHandler.js +111 -0
- data/lib/ui-auto-monkey/handler/wbScrollViewButtonHandler.js +114 -0
- data/lib/ui-auto-monkey/tuneup/LICENSE +20 -0
- data/lib/ui-auto-monkey/tuneup/assertions.js +402 -0
- data/lib/ui-auto-monkey/tuneup/image_asserter +26 -0
- data/lib/ui-auto-monkey/tuneup/image_assertion.js +65 -0
- data/lib/ui-auto-monkey/tuneup/image_assertion.rb +102 -0
- data/lib/ui-auto-monkey/tuneup/lang-ext.js +76 -0
- data/lib/ui-auto-monkey/tuneup/screen.js +11 -0
- data/lib/ui-auto-monkey/tuneup/test.js +71 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/abbreviated_console_output.rb +38 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/colored_console_output.rb +27 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/console_output.rb +17 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/preprocessor.rb +25 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/run +343 -0
- data/lib/ui-auto-monkey/tuneup/test_runner/xunit_output.rb +114 -0
- data/lib/ui-auto-monkey/tuneup/tuneup.js +6 -0
- data/lib/ui-auto-monkey/tuneup/tuneup_js.podspec +52 -0
- data/lib/ui-auto-monkey/tuneup/uiautomation-ext.js +965 -0
- data/smart_monkey.gemspec +112 -0
- data/spec/spec_helper.rb +12 -0
- metadata +192 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
class Preprocessor
|
2
|
+
def self.process(file)
|
3
|
+
self.process_imports(file, [])
|
4
|
+
end
|
5
|
+
|
6
|
+
private
|
7
|
+
IMPORT_STATEMENT = /#import "([^"]+)"/
|
8
|
+
def self.process_imports(file, imported_file_names)
|
9
|
+
content = File.read(file)
|
10
|
+
content.gsub(IMPORT_STATEMENT) do
|
11
|
+
next if imported_file_names.include? $1
|
12
|
+
|
13
|
+
imported_file_names << $1
|
14
|
+
import_file = File.join(File.dirname(file), $1)
|
15
|
+
begin
|
16
|
+
"// begin #{File.basename($1)}" << "\n" <<
|
17
|
+
process_imports(import_file, imported_file_names) << "\n" <<
|
18
|
+
"// end #{File.basename($1)}" << "\n"
|
19
|
+
rescue Exception => e
|
20
|
+
STDERR.puts "Unable to process file #{import_file}: #{e}"
|
21
|
+
$&
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
#!/usr/bin/env ruby -wKU
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "optparse"
|
5
|
+
require "ostruct"
|
6
|
+
require "pty"
|
7
|
+
require "tempfile"
|
8
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "preprocessor"))
|
9
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "console_output"))
|
10
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "colored_console_output"))
|
11
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "abbreviated_console_output"))
|
12
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "xunit_output"))
|
13
|
+
|
14
|
+
options = OpenStruct.new
|
15
|
+
options.env_vars = {}
|
16
|
+
options.attempts = 1
|
17
|
+
options.startup_timeout = 30
|
18
|
+
|
19
|
+
opts_parser = OptionParser.new do |opts|
|
20
|
+
opts.banner = "USAGE: run-test-script <app bundle> <test script> <output directory> [optional args]" +
|
21
|
+
"\n If <app bundle> is the name of the app without extension, the newest bundle is located autmatically"
|
22
|
+
|
23
|
+
opts.on("-d", "--device DEVICE", "Device UDID to run test against,",
|
24
|
+
"or 'dynamic' to find the UDID at runtime") do |dev|
|
25
|
+
options.device = dev
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-w", "--simName SIMULATORNAME", "Simulator name to target") do |dev|
|
29
|
+
options.simulatorName = dev
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-e", "--environment ENV", "Pass variables in the form of name=value") do |env|
|
33
|
+
key, value = env.split '='
|
34
|
+
options.env_vars[key] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
38
|
+
puts opts
|
39
|
+
exit 0
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("-t", "--timeout DURATION", OptionParser::DecimalInteger,
|
43
|
+
"Maximum time in seconds with no output") do |t|
|
44
|
+
options.timeout = t
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("-r", "--run TEST", "Only run tests named alike. It's possible to use RegExps.") do |test|
|
48
|
+
options.run = test
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-v", "--verbose", "Produce more output") do
|
52
|
+
options.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("-q", "--quiet", "Don't print test results to console") do
|
56
|
+
options.quiet = true
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on("-c", "--color", "Colorize the console output") do
|
60
|
+
options.colorize = true
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on("-b", "--brief", "Abbreviated and colorized console output") do
|
64
|
+
options.brief = true
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on("-p", "--preprocess", "Use own preprocessor to substitude imports") do
|
68
|
+
options.preprocess = true
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on("-x", "--xunit", "Create Xunit formatted test result file in the output directory") do
|
72
|
+
options.xunit = true;
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on("-l", "--language=en", "Use language to simulator") do |l|
|
76
|
+
options.language = "-AppleLanguages \(#{l}\)"
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on("-a", "--attempts ATTEMPTS", OptionParser::DecimalInteger,
|
80
|
+
"Number of times to attempt a successful test run in Instruments (reliability hack)") do |a|
|
81
|
+
options.attempts = a
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on("-s", "--startuptimeout DURATION", OptionParser::DecimalInteger,
|
85
|
+
"Amount of time to wait for output from Instruments scripts before judging it to be hung") do |t|
|
86
|
+
options.startup_timeout = t
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
unless ARGV.length >= 3
|
91
|
+
STDERR.puts opts_parser.help
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
|
95
|
+
app_bundle, test_script, test_output = ARGV.shift 3
|
96
|
+
|
97
|
+
begin
|
98
|
+
opts_parser.parse!
|
99
|
+
rescue OptionParser::ParseError => e
|
100
|
+
puts e
|
101
|
+
puts opts_parser
|
102
|
+
exit 1
|
103
|
+
end
|
104
|
+
|
105
|
+
# instrument cli helper does not recognise any relative directory.
|
106
|
+
# expand the relative directory and throw an exception if it does not texist.
|
107
|
+
test_output = File.expand_path(test_output)
|
108
|
+
raise "Output directory #{test_output} does not exist" unless File.directory?(test_output)
|
109
|
+
|
110
|
+
|
111
|
+
# Output options
|
112
|
+
outputs = []
|
113
|
+
unless options.quiet # HINT this could be moved into the actual options parsing
|
114
|
+
outputs << (options.colorize ? ColoredConsoleOutput.new : (options.brief ? AbbreviatedConsoleOutput.new : ConsoleOutput.new))
|
115
|
+
end
|
116
|
+
if options.xunit
|
117
|
+
report_filename = File.join(test_output, File.basename(test_script, File.extname(test_script)) + ".xml")
|
118
|
+
outputs << XunitOutput.new(report_filename)
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
# Dynamic UDID
|
124
|
+
# Discussion: better to have a separate command line option for dynamic?
|
125
|
+
if options.device then
|
126
|
+
if options.device.length == 40
|
127
|
+
# UDID provided
|
128
|
+
elsif options.device == "dynamic"
|
129
|
+
ioreg_output = `ioreg -w 0 -rc IOUSBDevice -k SupportsIPhoneOS`
|
130
|
+
if ioreg_output =~ /"USB Serial Number" = "([0-9a-z]+)"/ then
|
131
|
+
options.device = $1
|
132
|
+
puts "Using device with UDID #{options.device}", "\n" if options.verbose
|
133
|
+
else
|
134
|
+
raise "Couldn't get the UDID using ioreg"
|
135
|
+
end
|
136
|
+
else
|
137
|
+
raise "Invalid UDID with length #{options.device.length} provided"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Check for already running instrument processes
|
142
|
+
instrument_pids = `ps aux | grep -i instrument[s] | awk '{ print $2 }'`
|
143
|
+
if instrument_pids != "" then
|
144
|
+
warn "\n### There are other 'instrument' processes running that may interfere - consider terminating them first:\nkill -9 " + instrument_pids
|
145
|
+
end
|
146
|
+
|
147
|
+
# Find the bundle path at runtime
|
148
|
+
# Discussion: better to have a command line option for app_name,
|
149
|
+
# than using a "bare" app_bundle to specify app_name?
|
150
|
+
app_name = File.basename(app_bundle, ".*")
|
151
|
+
if app_bundle == app_name then
|
152
|
+
# List existing bundles, newest first
|
153
|
+
mdargs = 'kMDItemDisplayName == "' + app_name + '.app" || ' +
|
154
|
+
'kMDItemAlternateNames == "' + app_name + '.app"'
|
155
|
+
bundle_paths = `mdfind -0 '#{mdargs}' | xargs -0 ls -dt`.split(/\n/)
|
156
|
+
if options.device
|
157
|
+
bundles = bundle_paths.grep(/\/(Debug|Release)-iphoneos\//)
|
158
|
+
else
|
159
|
+
bundles = bundle_paths.grep(/\/iPhone Simulator\/|\/(Debug|Release)-iphonesimulator\//)
|
160
|
+
end
|
161
|
+
if bundles.size > 0
|
162
|
+
app_bundle = bundles[0]
|
163
|
+
puts "Found relevant bundles (newest first):", bundles, "\n" if options.verbose
|
164
|
+
puts "Using bundle:", app_bundle, "\n"
|
165
|
+
else
|
166
|
+
STDERR.puts "Matching but irrelevant bundles:", bundle_paths if bundle_paths.size > 0
|
167
|
+
raise "No output bundles found for app #{app_name}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Instruments wants the test_script and app_bundle to be a fully-qualified path
|
172
|
+
test_script = File.expand_path(test_script)
|
173
|
+
raise "Test script '#{test_script}' doesn't exist" unless File.exist?(test_script)
|
174
|
+
app_bundle = File.expand_path(app_bundle)
|
175
|
+
raise "App bundle '#{app_bundle}' doesn't exist" unless File.directory?(app_bundle)
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
if options.preprocess or options.run
|
180
|
+
temp_file = Tempfile.new(File.basename(test_script))
|
181
|
+
|
182
|
+
# inject test filter
|
183
|
+
if options.run
|
184
|
+
puts "Injecting test filter to only run test \"#{options.run}\"", "\n" if options.verbose
|
185
|
+
temp_file.write("var TUNEUP_ONLY_RUN = [\"#{options.run}\"];\n\n")
|
186
|
+
end
|
187
|
+
|
188
|
+
# apply custom preprocessing to test_script
|
189
|
+
if options.preprocess
|
190
|
+
puts "Preprocessing file..." if options.verbose
|
191
|
+
temp_file.write(Preprocessor.process test_script)
|
192
|
+
puts "contents written to temp file #{temp_file.path}", "\n" if options.verbose
|
193
|
+
end
|
194
|
+
|
195
|
+
temp_file.flush
|
196
|
+
test_script = temp_file.path
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
SDKROOT = `/usr/bin/xcodebuild -version -sdk iphoneos | grep PlatformPath`.split(":")[1].chomp.sub(/^\s+/, "")
|
201
|
+
XCODE_ROOT = `/usr/bin/xcode-select -print-path`.chomp.sub(/^\s+/, "")
|
202
|
+
|
203
|
+
# path is different for Instruments 6.0
|
204
|
+
if File.directory? "#{XCODE_ROOT}/../Applications/Instruments.app/Contents/PlugIns/AutomationInstrument.xrplugin/"
|
205
|
+
instruments_folder = "AutomationInstrument.xrplugin";
|
206
|
+
else
|
207
|
+
# older instruments path
|
208
|
+
instruments_folder = "AutomationInstrument.bundle";
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
TEMPLATE = `[ -f /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Instruments/PlugIns/#{instruments_folder}/Contents/Resources/Automation.tracetemplate ] && echo "#{SDKROOT}/Developer/Library/Instruments/PlugIns/#{instruments_folder}/Contents/Resources/Automation.tracetemplate" || echo "#{XCODE_ROOT}/../Applications/Instruments.app/Contents/PlugIns/#{instruments_folder}/Contents/Resources/Automation.tracetemplate"`.chomp.sub(/^\s+/, "")
|
213
|
+
|
214
|
+
command = ["env", "DEVELOPER_DIR=#{XCODE_ROOT}", "/usr/bin/instruments"]
|
215
|
+
if options.device
|
216
|
+
command << "-w" << options.device
|
217
|
+
elsif options.simulatorName
|
218
|
+
command << "-w" << options.simulatorName
|
219
|
+
end
|
220
|
+
|
221
|
+
command << "-t" << "#{TEMPLATE}"
|
222
|
+
command << app_bundle
|
223
|
+
command << "-e" << "UIASCRIPT" << test_script
|
224
|
+
command << "-e" << "UIARESULTSPATH" << test_output
|
225
|
+
options.env_vars.to_a.each do |pair|
|
226
|
+
command << "-e"
|
227
|
+
command.concat(pair)
|
228
|
+
end
|
229
|
+
command << options.language if options.language
|
230
|
+
puts "command=" + command.map { |s| s.sub(/\A(.*\s.*)\Z/, '"\1"') }.join(' ') if options.verbose
|
231
|
+
|
232
|
+
|
233
|
+
FileUtils.mkdir_p test_output unless File.directory? test_output
|
234
|
+
|
235
|
+
|
236
|
+
def parse_status(status)
|
237
|
+
case status
|
238
|
+
when /start/i then :start
|
239
|
+
when /pass/i then :pass
|
240
|
+
when /fail/i then :fail
|
241
|
+
when /error/i then :error
|
242
|
+
when /warning/i then :warning
|
243
|
+
when /issue/i then :issue
|
244
|
+
when /default/i then :default
|
245
|
+
else nil
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def failed?(statistics)
|
250
|
+
statistics[:total].to_i == 0 || statistics[:fail].to_i > 0 || statistics[:error].to_i > 0
|
251
|
+
end
|
252
|
+
|
253
|
+
def format_statistics(statistics)
|
254
|
+
output = "#{statistics[:total].to_i} tests, #{statistics[:fail].to_i} failures"
|
255
|
+
output << ", #{statistics[:error].to_i} errors" if statistics[:error].to_i > 0
|
256
|
+
output << ", #{statistics[:warning].to_i} warnings" if statistics[:warning].to_i > 0
|
257
|
+
output << ", #{statistics[:issue].to_i} issues" if statistics[:issue].to_i > 0
|
258
|
+
output
|
259
|
+
end
|
260
|
+
|
261
|
+
statistics = {}
|
262
|
+
failed = false
|
263
|
+
started = false
|
264
|
+
remaining_attempts = options.attempts
|
265
|
+
|
266
|
+
while (not started) && remaining_attempts > 0 do
|
267
|
+
statistics = {}
|
268
|
+
failed = false
|
269
|
+
remaining_attempts = remaining_attempts - 1
|
270
|
+
warn "\n### Launching instruments. Will retry #{remaining_attempts} more times if it doesn't initialize"
|
271
|
+
|
272
|
+
begin
|
273
|
+
Dir.chdir(test_output) do
|
274
|
+
|
275
|
+
PTY.spawn(*command) do |r, w, pid|
|
276
|
+
while not failed do
|
277
|
+
if not started then
|
278
|
+
mytimeout = options.startup_timeout
|
279
|
+
else
|
280
|
+
mytimeout = options.timeout
|
281
|
+
end
|
282
|
+
if IO.select([r], nil, nil, mytimeout) then
|
283
|
+
line = r.readline.rstrip
|
284
|
+
if (line.include? " +0000 ") && (not line.include? " +0000 Fail: The target application appears to have died") then
|
285
|
+
started = true
|
286
|
+
end
|
287
|
+
|
288
|
+
_, date, time, tz, status, msg = line.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) ([+-]\d{4}) ([^:]+): (.*)$/).to_a
|
289
|
+
|
290
|
+
if status
|
291
|
+
if options.verbose || status != "Debug"
|
292
|
+
status = parse_status(status) if status
|
293
|
+
outputs.each { |output| output.add_status(status, date, time, tz, msg) }
|
294
|
+
statistics[:total] = statistics[:total].to_i + 1 if status == :start
|
295
|
+
statistics[status] = statistics[status].to_i + 1
|
296
|
+
end
|
297
|
+
else
|
298
|
+
outputs.each { |output| output.add(line) } if options.verbose
|
299
|
+
end
|
300
|
+
|
301
|
+
failed = true if line =~ /Instruments Trace Error/i
|
302
|
+
else
|
303
|
+
failed = true
|
304
|
+
warn "\n### Timeout #{options.timeout} reached without any output - " +
|
305
|
+
"killing Instruments (pid #{pid})..."
|
306
|
+
begin
|
307
|
+
Process.kill(9, pid)
|
308
|
+
w.close
|
309
|
+
r.close
|
310
|
+
Process.wait(pid)
|
311
|
+
rescue PTY::ChildExited
|
312
|
+
end
|
313
|
+
puts "Pid #{pid} killed."
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
rescue Errno::EIO
|
320
|
+
rescue Errno::ECHILD
|
321
|
+
rescue EOFError
|
322
|
+
rescue PTY::ChildExited
|
323
|
+
STDERR.puts "Instruments exited unexpectedly"
|
324
|
+
exit 1 if started
|
325
|
+
ensure
|
326
|
+
outputs.map(&:close)
|
327
|
+
|
328
|
+
if temp_file
|
329
|
+
temp_file.close
|
330
|
+
temp_file.unlink
|
331
|
+
puts "Deleted temp file" if options.verbose
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
if failed || failed?(statistics)
|
337
|
+
STDERR.puts format_statistics(statistics)
|
338
|
+
STDERR.puts "#{test_script} failed, see log output for details"
|
339
|
+
exit 1
|
340
|
+
else
|
341
|
+
STDOUT.puts format_statistics(statistics)
|
342
|
+
STDOUT.puts "TEST PASSED"
|
343
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class TestSuite
|
4
|
+
attr_reader :name, :timestamp
|
5
|
+
attr_accessor :test_cases
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@test_cases = []
|
10
|
+
@timestamp = DateTime.now
|
11
|
+
end
|
12
|
+
|
13
|
+
def failures
|
14
|
+
@test_cases.count { |test| test.failed? }
|
15
|
+
end
|
16
|
+
|
17
|
+
def time
|
18
|
+
@test_cases.map { |test| test.time }.inject(:+)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class TestCase
|
23
|
+
attr_reader :name
|
24
|
+
attr_accessor :messages
|
25
|
+
|
26
|
+
def initialize(name)
|
27
|
+
@name = name
|
28
|
+
@messages = []
|
29
|
+
@failed = true
|
30
|
+
@start = Time.now
|
31
|
+
@finish = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def <<(message)
|
35
|
+
@messages << message
|
36
|
+
end
|
37
|
+
|
38
|
+
def pass!
|
39
|
+
@failed = false;
|
40
|
+
@finish = Time.now
|
41
|
+
end
|
42
|
+
|
43
|
+
def fail!
|
44
|
+
@finish = Time.now
|
45
|
+
end
|
46
|
+
|
47
|
+
def failed?
|
48
|
+
@failed
|
49
|
+
end
|
50
|
+
|
51
|
+
def time
|
52
|
+
return 0 if @finish.nil?
|
53
|
+
@finish - @start
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Creates a XML report that conforms to # https://svn.jenkins-ci.org/trunk/hudson/dtkit/dtkit-format/dtkit-junit-model/src/main/resources/com/thalesgroup/dtkit/junit/model/xsd/junit-4.xsd
|
58
|
+
class XunitOutput
|
59
|
+
def initialize(filename)
|
60
|
+
@filename = filename
|
61
|
+
@suite = TestSuite.new(File.basename(filename, File.extname(filename)))
|
62
|
+
end
|
63
|
+
|
64
|
+
def add(line)
|
65
|
+
return if @suite.test_cases.empty?
|
66
|
+
@suite.test_cases.last << line
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_status(status, date, time, time_zone, msg)
|
70
|
+
case status
|
71
|
+
when :start
|
72
|
+
@suite.test_cases << TestCase.new(msg)
|
73
|
+
when :pass
|
74
|
+
@suite.test_cases.last.pass! if @suite.test_cases.last != nil
|
75
|
+
when :fail
|
76
|
+
@suite.test_cases.last.fail! if @suite.test_cases.last != nil
|
77
|
+
else
|
78
|
+
if @suite.test_cases.last != nil && @suite.test_cases.last.time == 0
|
79
|
+
@suite.test_cases.last << "#{status.to_s.capitalize}: #{msg}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def close
|
85
|
+
File.open(@filename, 'w') { |f| f.write(serialize(@suite)) }
|
86
|
+
end
|
87
|
+
|
88
|
+
def xml_escape(input)
|
89
|
+
result = input.dup
|
90
|
+
|
91
|
+
result.gsub!("&", "&")
|
92
|
+
result.gsub!("<", "<")
|
93
|
+
result.gsub!(">", ">")
|
94
|
+
result.gsub!("'", "'")
|
95
|
+
result.gsub!("\"", """)
|
96
|
+
|
97
|
+
return result
|
98
|
+
end
|
99
|
+
|
100
|
+
def serialize(suite)
|
101
|
+
output = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" << "\n"
|
102
|
+
output << "<testsuite name=\"#{xml_escape(suite.name)}\" timestamp=\"#{suite.timestamp}\" time=\"#{suite.time}\" tests=\"#{suite.test_cases.count}\" failures=\"#{suite.failures}\">" << "\n"
|
103
|
+
|
104
|
+
suite.test_cases.each do |test|
|
105
|
+
output << " <testcase name=\"#{xml_escape(test.name)}\" time=\"#{test.time}\">" << "\n"
|
106
|
+
if test.failed?
|
107
|
+
output << " <failure>#{test.messages.map { |m| xml_escape(m) }.join("\n")}</failure>" << "\n"
|
108
|
+
end
|
109
|
+
output << " </testcase>" << "\n"
|
110
|
+
end
|
111
|
+
|
112
|
+
output << "</testsuite>" << "\n"
|
113
|
+
end
|
114
|
+
end
|