asautotest 0.0.1
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.
- data/LICENSE +674 -0
- data/README.rdoc +182 -0
- data/bin/asautotest +437 -0
- data/bin/flash-policy-server +88 -0
- data/lib/asautotest/compilation-output-parser.rb +115 -0
- data/lib/asautotest/compilation-result.rb +102 -0
- data/lib/asautotest/compilation-runner.rb +135 -0
- data/lib/asautotest/compiler-shell.rb +78 -0
- data/lib/asautotest/logging.rb +124 -0
- data/lib/asautotest/problematic-file.rb +526 -0
- data/lib/asautotest/stopwatch.rb +50 -0
- data/lib/asautotest/test-runner.rb +424 -0
- data/lib/asautotest/utilities.rb +62 -0
- metadata +78 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# stopwatch.rb --- utility class for measuring delays
|
3
|
+
# Copyright (C) 2010 Go Interactive
|
4
|
+
|
5
|
+
# This file is part of ASAutotest.
|
6
|
+
|
7
|
+
# ASAutotest is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
|
12
|
+
# ASAutotest is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with ASAutotest. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
module ASAutotest
|
21
|
+
class Stopwatch
|
22
|
+
def initialize
|
23
|
+
@start_time = current_time
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s(decimals = 3)
|
27
|
+
round(n_elapsed_seconds, decimals).to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@end_time = current_time
|
32
|
+
end
|
33
|
+
|
34
|
+
def end_time
|
35
|
+
@end_time || current_time
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_time
|
39
|
+
Time.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def n_elapsed_seconds
|
43
|
+
end_time - @start_time
|
44
|
+
end
|
45
|
+
|
46
|
+
def round(number, n_decimals)
|
47
|
+
(number * 10 ** n_decimals).round.to_f / 10 ** n_decimals
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,424 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# test-runner.rb --- run tests and report the results
|
3
|
+
# Copyright (C) 2010 Go Interactive
|
4
|
+
|
5
|
+
# This file is part of ASAutotest.
|
6
|
+
|
7
|
+
# ASAutotest is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
|
12
|
+
# ASAutotest is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
|
17
|
+
# You should have received a copy of the GNU General Public License
|
18
|
+
# along with ASAutotest. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
require "socket"
|
21
|
+
require "timeout"
|
22
|
+
require "rexml/document"
|
23
|
+
|
24
|
+
module ASAutotest
|
25
|
+
class TestRunner
|
26
|
+
class TestMisbehaving < Exception ; end
|
27
|
+
class TestMisbehavingFatally < Exception ; end
|
28
|
+
|
29
|
+
include Logging
|
30
|
+
|
31
|
+
EXPECTED_GREETING = "Hello, this is a test.\n"
|
32
|
+
POLICY_FILE_REQUEST = "<policy-file-request/>\0"
|
33
|
+
|
34
|
+
# Make sure we can accept a policy file request as a greeting.
|
35
|
+
EXPECTED_GREETING.size >= POLICY_FILE_REQUEST.size or
|
36
|
+
raise "Internal error: Expected greeting is too short."
|
37
|
+
|
38
|
+
def initialize(binary_name, port)
|
39
|
+
@binary_name = binary_name
|
40
|
+
@port = port
|
41
|
+
@n_planned_tests = nil
|
42
|
+
@suites = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def run
|
46
|
+
whisper "Running tests via socket connection."
|
47
|
+
with_server_running { run_test }
|
48
|
+
rescue TestMisbehaving
|
49
|
+
shout "Terminating misbehaving test."
|
50
|
+
rescue TestMisbehavingFatally
|
51
|
+
exit -1
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_test
|
55
|
+
with_flash_running do
|
56
|
+
accept_connection
|
57
|
+
shake_hands
|
58
|
+
talk_to_test
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# ------------------------------------------------------
|
63
|
+
|
64
|
+
def with_server_running
|
65
|
+
start_server
|
66
|
+
begin
|
67
|
+
yield
|
68
|
+
ensure
|
69
|
+
stop_server
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def start_server
|
74
|
+
@server = TCPServer.new(@port)
|
75
|
+
@server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
76
|
+
end
|
77
|
+
|
78
|
+
def stop_server
|
79
|
+
@server.close
|
80
|
+
end
|
81
|
+
|
82
|
+
# ------------------------------------------------------
|
83
|
+
|
84
|
+
def with_flash_running
|
85
|
+
start_flash
|
86
|
+
begin
|
87
|
+
yield
|
88
|
+
ensure
|
89
|
+
stop_flash
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def start_flash
|
94
|
+
@flash_pid = fork { exec FLASHPLAYER, @binary_name }
|
95
|
+
end
|
96
|
+
|
97
|
+
def stop_flash
|
98
|
+
Process.kill("TERM", @flash_pid)
|
99
|
+
Process.wait(@flash_pid)
|
100
|
+
end
|
101
|
+
|
102
|
+
# ------------------------------------------------------
|
103
|
+
|
104
|
+
def misbehavior!(*descriptions)
|
105
|
+
print_warnings(descriptions)
|
106
|
+
raise TestMisbehaving
|
107
|
+
end
|
108
|
+
|
109
|
+
def fatal_misbehavior!(*descriptions)
|
110
|
+
print_warnings(descriptions)
|
111
|
+
raise TestMisbehavingFatally
|
112
|
+
end
|
113
|
+
|
114
|
+
def print_warnings(warnings)
|
115
|
+
for warning in warnings do
|
116
|
+
shout warning
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# ------------------------------------------------------
|
121
|
+
|
122
|
+
def accept_connection
|
123
|
+
whisper "Accepting connection" do
|
124
|
+
# It takes at least 3 seconds to get a policy file request.
|
125
|
+
@socket = Timeout.timeout(4) { @server.accept }
|
126
|
+
end
|
127
|
+
rescue Timeout::Error
|
128
|
+
misbehavior! "Test did not connect to localhost:#@port."
|
129
|
+
end
|
130
|
+
|
131
|
+
def shake_hands
|
132
|
+
Timeout.timeout(1) { parse_greeting(read_greeting) }
|
133
|
+
rescue Timeout::Error
|
134
|
+
misbehavior! "Handshake took too long."
|
135
|
+
end
|
136
|
+
|
137
|
+
def read_greeting
|
138
|
+
@socket.read(EXPECTED_GREETING.size)
|
139
|
+
end
|
140
|
+
|
141
|
+
def parse_greeting(greeting)
|
142
|
+
case greeting
|
143
|
+
when EXPECTED_GREETING
|
144
|
+
whisper "Performed handshake."
|
145
|
+
when nil
|
146
|
+
misbehavior! "Test closed connection without sending anything."
|
147
|
+
when POLICY_FILE_REQUEST
|
148
|
+
fatal_misbehavior! \
|
149
|
+
"Received cross-domain policy file request; aborting.",
|
150
|
+
"Please run a policy server on port 843 (root usually needed).",
|
151
|
+
"See ‘bin/policy-server.rb’ in the ASAutotest distribution."
|
152
|
+
else
|
153
|
+
misbehavior! "Unrecognized greeting: #{greeting.inspect}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def talk_to_test
|
158
|
+
@test_stopwatch = Stopwatch.new
|
159
|
+
Timeout.timeout(10) { talk_patiently_to_test }
|
160
|
+
report_results
|
161
|
+
rescue Timeout::Error
|
162
|
+
misbehavior! "Test run taking too long; aborting."
|
163
|
+
end
|
164
|
+
|
165
|
+
def report_results
|
166
|
+
if failed_tests?
|
167
|
+
suites.each &:print_report!
|
168
|
+
puts
|
169
|
+
else
|
170
|
+
say test_count_report
|
171
|
+
shout "Missing #{n_missing_tests} tests." if missing_tests?
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_count_report
|
176
|
+
build_string do |result|
|
177
|
+
result << "Ran #{n_completed_tests} tests"
|
178
|
+
|
179
|
+
if new_tests?
|
180
|
+
result << " (#{n_new_tests} new)"
|
181
|
+
elsif missing_tests?
|
182
|
+
result << " (too few)"
|
183
|
+
end
|
184
|
+
|
185
|
+
result << " in ~#@test_stopwatch seconds."
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def talk_patiently_to_test
|
190
|
+
catch(:done) do
|
191
|
+
loop do
|
192
|
+
line = @socket.readline.chomp
|
193
|
+
case line
|
194
|
+
when /^plan (\d+)$/
|
195
|
+
if @n_planned_tests != nil
|
196
|
+
misbehavior! "Got another plan: #{line.inspect}"
|
197
|
+
elsif n_completed_tests > 0
|
198
|
+
misbehavior! "Got plan too late: #{line.inspect}"
|
199
|
+
else
|
200
|
+
@n_planned_tests = $1.to_i
|
201
|
+
whisper "Planning to run #{@n_planned_tests} tests."
|
202
|
+
end
|
203
|
+
when "done"
|
204
|
+
throw :done
|
205
|
+
when /^xml-result: (.*)/
|
206
|
+
begin
|
207
|
+
result = Result.parse_xml($1)
|
208
|
+
get_suite(result.suite_name) << result
|
209
|
+
rescue Result::ParseError
|
210
|
+
misbehavior! "Could not interpret XML result: #$1"
|
211
|
+
end
|
212
|
+
else
|
213
|
+
puts ">> #{line.inspect}"
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def n_completed_tests
|
220
|
+
suites.map(&:n_results).sum
|
221
|
+
end
|
222
|
+
|
223
|
+
def n_failed_tests
|
224
|
+
suites.map(&:n_failures).sum
|
225
|
+
end
|
226
|
+
|
227
|
+
def suites
|
228
|
+
@suites.values
|
229
|
+
end
|
230
|
+
|
231
|
+
def get_suite(name)
|
232
|
+
if @suites.include? name
|
233
|
+
@suites[name]
|
234
|
+
else
|
235
|
+
@suites[name] = Suite.new(name)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
class Suite
|
240
|
+
attr_reader :name
|
241
|
+
|
242
|
+
def initialize(name)
|
243
|
+
@name = name
|
244
|
+
@results = []
|
245
|
+
end
|
246
|
+
|
247
|
+
def << result
|
248
|
+
@results << result
|
249
|
+
end
|
250
|
+
|
251
|
+
def print_report!
|
252
|
+
if @results.any? &:failed?
|
253
|
+
print_header!
|
254
|
+
@results.each &:print_report!
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def print_header!
|
259
|
+
puts
|
260
|
+
puts "\e[1m#{display_name}\e[0m"
|
261
|
+
end
|
262
|
+
|
263
|
+
def display_name
|
264
|
+
@name or "(Unnamed suite)"
|
265
|
+
end
|
266
|
+
|
267
|
+
def n_results
|
268
|
+
@results.size
|
269
|
+
end
|
270
|
+
|
271
|
+
def n_failures
|
272
|
+
@results.count &:failed?
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
class Result
|
277
|
+
class ParseError < Exception ; end
|
278
|
+
|
279
|
+
include Logging
|
280
|
+
|
281
|
+
attr_reader :test_name
|
282
|
+
|
283
|
+
def initialize(test_name)
|
284
|
+
@test_name = test_name
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.parse_xml(input)
|
288
|
+
XMLResult.new(REXML::Document.new(input).root).result
|
289
|
+
rescue
|
290
|
+
raise ParseError
|
291
|
+
end
|
292
|
+
|
293
|
+
class XMLResult
|
294
|
+
def initialize(root)
|
295
|
+
@root = root
|
296
|
+
end
|
297
|
+
|
298
|
+
def result
|
299
|
+
case @root.name
|
300
|
+
when "success"
|
301
|
+
Success.new(test_name)
|
302
|
+
when "failure"
|
303
|
+
failure
|
304
|
+
else
|
305
|
+
raise ParseError
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_name
|
310
|
+
@root.attributes["test-name"] or raise ParseError
|
311
|
+
end
|
312
|
+
|
313
|
+
def failure
|
314
|
+
case failure_type
|
315
|
+
when "equality"
|
316
|
+
Failure::Equality.new \
|
317
|
+
test_name,
|
318
|
+
failure_attribute("expected"),
|
319
|
+
failure_attribute("actual")
|
320
|
+
else
|
321
|
+
Failure::Simple.new(test_name, description)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def failure_type
|
326
|
+
failure_element.name if failure_element
|
327
|
+
end
|
328
|
+
|
329
|
+
def description
|
330
|
+
@root.attributes["description"]
|
331
|
+
end
|
332
|
+
|
333
|
+
def failure_attribute(name)
|
334
|
+
failure_element.attributes[name] or raise ParseError
|
335
|
+
end
|
336
|
+
|
337
|
+
def failure_element
|
338
|
+
@root.elements[1]
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def passed? ; not failed? end
|
343
|
+
|
344
|
+
def local_name
|
345
|
+
local_and_suite_names[1]
|
346
|
+
end
|
347
|
+
|
348
|
+
def suite_name
|
349
|
+
local_and_suite_names[2]
|
350
|
+
end
|
351
|
+
|
352
|
+
def local_and_suite_names
|
353
|
+
test_name.match /^(.*?)(?: \((\S+)\))?$/
|
354
|
+
end
|
355
|
+
|
356
|
+
class Success < Result
|
357
|
+
def failed? ; false end
|
358
|
+
|
359
|
+
def print_report!
|
360
|
+
whisper "Passed: #{test_name}"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
class Failure < Result
|
365
|
+
def failed? ; true end
|
366
|
+
|
367
|
+
def print_report!
|
368
|
+
puts " \e[1;31mFailed:\e[0m \e[0;4m#{local_name}\e[0m"
|
369
|
+
report_reason!
|
370
|
+
end
|
371
|
+
|
372
|
+
def report_reason! ; end
|
373
|
+
|
374
|
+
class Simple < Failure
|
375
|
+
def initialize(test_name, description)
|
376
|
+
super(test_name)
|
377
|
+
@description = description
|
378
|
+
end
|
379
|
+
|
380
|
+
def report_reason!
|
381
|
+
puts " \e[0m#@description\e[0m" if @description
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
class Equality < Failure
|
386
|
+
def initialize(test_name, expected, actual)
|
387
|
+
super(test_name)
|
388
|
+
@expected = expected
|
389
|
+
@actual = actual
|
390
|
+
end
|
391
|
+
|
392
|
+
def report_reason!
|
393
|
+
puts " \e[0mActual:\e[0m \e[0m#@actual\e[0m"
|
394
|
+
puts " \e[0mExpected:\e[0m \e[0m#@expected\e[0m"
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
# ------------------------------------------------------
|
401
|
+
|
402
|
+
attr_reader :n_planned_tests
|
403
|
+
|
404
|
+
def n_missing_tests
|
405
|
+
n_planned_tests - n_completed_tests
|
406
|
+
end
|
407
|
+
|
408
|
+
def n_new_tests
|
409
|
+
n_completed_tests - n_planned_tests
|
410
|
+
end
|
411
|
+
|
412
|
+
def new_tests?
|
413
|
+
n_new_tests > 0
|
414
|
+
end
|
415
|
+
|
416
|
+
def missing_tests?
|
417
|
+
n_missing_tests > 0
|
418
|
+
end
|
419
|
+
|
420
|
+
def failed_tests?
|
421
|
+
n_failed_tests > 0
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|