asautotest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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