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.
@@ -0,0 +1,124 @@
1
+ # -*- coding: utf-8 -*-
2
+ # logging.rb --- mixins for logging and various other utilities
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
+ module Bracketable
22
+ def [] *arguments ; new(*arguments) end
23
+ end
24
+
25
+ module Logging
26
+ PREFIX = "asautotest: "
27
+
28
+ def self.verbose= value
29
+ @verbose = value
30
+ end
31
+
32
+ def self.verbose?
33
+ @verbose == true
34
+ end
35
+
36
+ def verbose?
37
+ Logging.verbose?
38
+ end
39
+
40
+ # ------------------------------------------------------
41
+
42
+ def say(*arguments, &block)
43
+ if block_given?
44
+ say_with_block(*arguments, &block)
45
+ else
46
+ say_without_block(*arguments)
47
+ end
48
+ end
49
+
50
+ def shout(message)
51
+ say "\e[1;31m!!\e[0m #{message}"
52
+ end
53
+
54
+ def hint(message)
55
+ say "\e[34m#{message}\e[0m"
56
+ end
57
+
58
+ def whisper(*arguments, &block)
59
+ if block_given?
60
+ whisper_with_block(*arguments, &block)
61
+ else
62
+ whisper_without_block(*arguments)
63
+ end
64
+ end
65
+
66
+ def barf(message)
67
+ puts "\e[1;31m??\e[0m #{message}"
68
+ end
69
+
70
+ # ------------------------------------------------------
71
+
72
+ def say_without_block(message)
73
+ puts "#{PREFIX}#{message}"
74
+ end
75
+
76
+ def say_with_block(message, ok_message = "ok", error_message = "failed")
77
+ start_saying(message)
78
+ status = ""
79
+ yield(status)
80
+ end_saying(status.empty? ? ok_message : status)
81
+ ended = true
82
+ ensure
83
+ end_saying(error_message) unless ended
84
+ end
85
+
86
+ def start_saying(message)
87
+ STDOUT.print "#{PREFIX}#{message}..."
88
+ STDOUT.flush
89
+ end
90
+
91
+ def end_saying(message)
92
+ STDOUT.puts " #{message}."
93
+ end
94
+
95
+ # ------------------------------------------------------
96
+
97
+ def whisper_without_block(message)
98
+ say message if verbose?
99
+ end
100
+
101
+ def whisper_with_block(message, ok_message = "ok", error_message = "failed")
102
+ start_whisper(message)
103
+ yield
104
+ end_whisper(ok_message)
105
+ ended = true
106
+ ensure
107
+ end_whisper(error_message) unless ended
108
+ end
109
+
110
+ def start_whisper(message)
111
+ start_saying(message) if verbose?
112
+ end
113
+
114
+ def end_whisper(message)
115
+ end_saying(message) if verbose?
116
+ end
117
+
118
+ # ------------------------------------------------------
119
+
120
+ def new_logging_section
121
+ say("—" * 60)
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,526 @@
1
+ # -*- coding: utf-8 -*-
2
+ # problematic-file.rb --- parsing of compiler errors and warnings
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 ProblematicFile
22
+ include Logging
23
+
24
+ def initialize(file_name, source_directories)
25
+ @file_name = file_name
26
+ @source_directories = source_directories
27
+ @problems = []
28
+ end
29
+
30
+ def << problem
31
+ @problems << problem
32
+ problem.file = self
33
+ @problems = @problems.sort_by { |x| x.sort_key }
34
+ end
35
+
36
+ def n_problems
37
+ @problems.size
38
+ end
39
+
40
+ def type
41
+ Type[dirname.gsub(/\/|\\/, "."), basename.sub(/\..*$/, "")]
42
+ end
43
+
44
+ def any_type_warnings?
45
+ @problems.any? &:type_warning?
46
+ end
47
+
48
+ def problem_before(next_problem)
49
+ returning nil do
50
+ previous_problem = nil
51
+ for problem in @problems do
52
+ if problem == next_problem
53
+ return previous_problem
54
+ else
55
+ previous_problem = problem
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def print_report
62
+ puts
63
+ print ljust("\e[1m#{basename}\e[0m", 40)
64
+ print " (in #{dirname})" unless dirname == "." if has_file_name?
65
+ puts
66
+
67
+ @problems.each &:print_report
68
+ end
69
+
70
+ def has_file_name?
71
+ @file_name != nil
72
+ end
73
+
74
+ def basename
75
+ if @file_name == nil
76
+ "(compiler error)"
77
+ else
78
+ File.basename(@file_name)
79
+ end
80
+ end
81
+
82
+ def dirname
83
+ File.dirname(stripped_file_name) if has_file_name?
84
+ end
85
+
86
+ def stripped_file_name
87
+ if source_directory
88
+ @file_name[source_directory.size .. -1]
89
+ else
90
+ @file_name
91
+ end
92
+ end
93
+
94
+ def source_directory
95
+ @source_directories.find do |directory|
96
+ @file_name.start_with? directory
97
+ end
98
+ end
99
+ end
100
+
101
+ class Location
102
+ extend Bracketable
103
+ attr_reader :line_number, :column_number, :source_line
104
+
105
+ def initialize(line_number, column_number, source_line)
106
+ @line_number = line_number
107
+ @column_number = column_number
108
+ @source_line = source_line
109
+ end
110
+
111
+ def sort_key
112
+ [line_number, column_number]
113
+ end
114
+ end
115
+
116
+ class Type
117
+ extend Bracketable
118
+ attr_reader :package, :name
119
+ def initialize(package, name)
120
+ @package = package
121
+ @name = name
122
+ end
123
+
124
+ def self.parse(input)
125
+ case input
126
+ when /^(.*):(.*)$/
127
+ Type[$1, $2]
128
+ else
129
+ Type[nil, input]
130
+ end
131
+ end
132
+
133
+ def to_s
134
+ name ? name : package
135
+ end
136
+
137
+ def full_name
138
+ "#{package}.#{name}"
139
+ end
140
+
141
+ def == other
142
+ package == other.package and name == other.name
143
+ end
144
+ end
145
+
146
+ class Member
147
+ extend Bracketable
148
+ attr_reader :type, :name
149
+
150
+ def initialize(type, name)
151
+ @type = type
152
+ @name = name
153
+ end
154
+
155
+ def to_s
156
+ @type ? "#{@type.name}.#@name" : "#@name"
157
+ end
158
+ end
159
+
160
+ class Problem
161
+ attr_accessor :location
162
+ attr_accessor :file
163
+
164
+ def details ; nil end
165
+ def extra_details ; nil end
166
+ def type_warning? ; false end
167
+
168
+ def source_line
169
+ if location
170
+ build_string do |result|
171
+ result << location.source_line.trim
172
+ result << " ..." unless location.source_line =~ /[;{}]\s*$/
173
+ end
174
+ end
175
+ end
176
+
177
+ def sort_key
178
+ location ? location.sort_key : [0]
179
+ end
180
+
181
+ def line_number
182
+ location.line_number rescue "?"
183
+ end
184
+
185
+ def column_number
186
+ location.column_number - indentation_width rescue "?"
187
+ end
188
+
189
+ def indentation_width
190
+ location.source_line =~ /^(\s*)/ ; $1.size
191
+ end
192
+
193
+ def self.[] message, location
194
+ returning parse(message) do |problem|
195
+ problem.location = location
196
+ end
197
+ end
198
+
199
+ def self.parse(message)
200
+ case message.sub(/^(Error|Warning):\s+/, "")
201
+ when /^Definition (\S+) could not be found.$/i
202
+ UndefinedImport.new(Type.parse($1))
203
+ when /^Call to a possibly undefined method (\S+) .* type (\S+).$/i
204
+ UndefinedMethod.new(Member[Type.parse($2), $1])
205
+ when /^Call to a possibly undefined method (\S+).$/i
206
+ UndefinedMethod.new(Member[nil, $1])
207
+ when /^Access of undefined property (\S+).$/i
208
+ UndefinedProperty.new(Member[nil, $1])
209
+ when /^Access of possibly undefined property (\S+)/i
210
+ UndefinedProperty.new(Member[nil, $1])
211
+ when /^Attempted access of inaccessible property (\S+)/i
212
+ InaccessibleProperty.new(Member[nil, $1])
213
+ when /^A file found in a source-path must have .*? '(\S+?)'/i
214
+ WrongPackage.new(Type[$1, nil])
215
+ when /^A file found in a source-path '(\S+?)' must .* as the class/i
216
+ WrongClassName.new(Type[nil, $1])
217
+ when /^Type was not found or was not a compile-time constant: (\S+).$/i
218
+ UndefinedType.new(Type.parse($1))
219
+ when /^The definition of base class (\S+) was not found.$/i
220
+ UndefinedType.new(Type.parse($1))
221
+ when /^Illegal assignment to a variable specified as constant.$/i
222
+ ConstantAssignment.new
223
+ when /^Method marked override must override another method.$/i
224
+ BogusOverride.new
225
+ when /^Incorrect number of arguments.\s* Expected no more than (\d+).$/i
226
+ TooManyArguments.new($1.to_i)
227
+ when /^Incorrect number of arguments.\s* Expected (0).$/i
228
+ TooManyArguments.new($1.to_i)
229
+ when /^Incorrect number of arguments.\s* Expected (\d+).$/i
230
+ TooFewArguments.new($1.to_i)
231
+ when /^return value for function '(\S+)' has no type declaration.$/i
232
+ MissingReturnType.new(Member[nil, $1])
233
+ when /^(?:variable|parameter) '(\S+)' has no type declaration.$/i
234
+ MissingTypeDeclaration.new
235
+ when /^Interface (\S+) was not found.$/i
236
+ InterfaceNotFound.new(Type.parse($1))
237
+ when /^Implicit coercion of a value of type (\S+) to an unrelated type (\S+).$/i
238
+ TypeMismatch.new(Type.parse($2), Type.parse($1))
239
+ when /^Comparison between a value with static type (\S+) and a possibly unrelated type Null.$/i
240
+ InvalidNullComparison.new
241
+ when /^Interface method ((?:get |set )?\S+) in namespace (\S+) not implemented by class (\S+).$/i
242
+ MissingImplementation.new(Member[Type.parse($2), $1], Type.parse($3))
243
+ when /^Interface method ((?:get |set )?\S+) in namespace (\S+) is implemented with an incompatible signature in class (\S+).$/i
244
+ WrongImplementation.new(Member[Type.parse($2), $1], Type.parse($3))
245
+ when /^A file (.*) must have an externally visible definition./
246
+ MissingPublicDefinition.new
247
+ else
248
+ Unknown.new(message)
249
+ end
250
+ end
251
+
252
+ def detail ; nil end
253
+ def plain_message
254
+ if detail
255
+ "#{message} #{detail}"
256
+ else
257
+ message.sub(/:$/, ".")
258
+ end
259
+ end
260
+
261
+ class ConstantAssignment < Problem
262
+ def message ; "Attempt to modify constant:" end
263
+ def details ; identifier_source_line_details end
264
+ end
265
+
266
+ class BogusOverride < Problem
267
+ def message ; "Bogus override:" end
268
+ def details ; identifier_source_line_details end
269
+ end
270
+
271
+ class UndefinedImport < Problem
272
+ def initialize(type) @type = type end
273
+ def message ; "Import not found:" end
274
+ def details ; bullet_details(detail) end
275
+ def detail ; @type.full_name end
276
+ end
277
+
278
+ class UndefinedMethod < Problem
279
+ def initialize(member) @member = member end
280
+ def message ; "Undefined method:" end
281
+ def details ; identifier_source_line_details end
282
+ def detail ; @member end
283
+ end
284
+
285
+ class UndefinedProperty < Problem
286
+ def initialize(member) @member = member end
287
+ def message ; "Undefined property:" end
288
+ def details ; identifier_source_line_details end
289
+ def detail ; @member end
290
+ end
291
+
292
+ class InaccessibleProperty < Problem
293
+ def initialize(member) @member = member end
294
+ def message ; "Property access not allowed:" end
295
+ def details ; identifier_source_line_details end
296
+ def detail ; @member end
297
+ end
298
+
299
+ class WrongPackage < Problem
300
+ def initialize(type) @type = type end
301
+ def message ; "Package should be #@type." end
302
+ end
303
+
304
+ class WrongClassName < Problem
305
+ def initialize(type) @type = type end
306
+ def message ; "Class name should be #@type." end
307
+ end
308
+
309
+ class UndefinedType < Problem
310
+ def initialize(type) @type = type end
311
+ def message ; "Undefined type:" end
312
+ def details ; identifier_source_line_details end
313
+ def detail ; @type.full_name end
314
+ def extra_details
315
+ if problematic_identifier == "Sprite"
316
+ "Hint: It’s flash.display.Sprite."
317
+ end
318
+ end
319
+ end
320
+
321
+ class TooManyArguments < Problem
322
+ def initialize(max) @max = max end
323
+ def message
324
+ if @max == 0
325
+ "No arguments allowed:"
326
+ else
327
+ "Too many arguments (only #@max allowed):"
328
+ end
329
+ end
330
+ def details ; source_line_details end
331
+ end
332
+
333
+ class TooFewArguments < Problem
334
+ def initialize(min) @min = min end
335
+ def message ; "Too few arguments (expected #@min):" end
336
+ def details ; source_line_details end
337
+ end
338
+
339
+ class MissingReturnType < Problem
340
+ def initialize(member) @member = member end
341
+ def message ; "Missing return type:" end
342
+ def detail ; @member end
343
+ def details ; member_details end
344
+ def type_warning? ; true end
345
+ end
346
+
347
+ class MissingTypeDeclaration < Problem
348
+ def message ; "Missing type:" end
349
+ def details ; identifier_source_line_details end
350
+ def type_warning? ; true end
351
+ end
352
+
353
+ class InterfaceNotFound < Problem
354
+ def initialize(member) @member = member end
355
+ def message ; "Interface not found:" end
356
+ def detail ; @member end
357
+ def details ; member_details end
358
+ end
359
+
360
+ class InvalidNullComparison < Problem
361
+ def message ; "Invalid comparison to null." end
362
+ def details ; source_line_details end
363
+ end
364
+
365
+ class TypeMismatch < Problem
366
+ def initialize(expected_type, actual_type)
367
+ @expected_type = expected_type
368
+ @actual_type = actual_type
369
+ end
370
+
371
+ def message
372
+ "Expected \e[4m#@expected_type\e[0m " +
373
+ "but was \e[4m#@actual_type\e[0m:"
374
+ end
375
+
376
+ def details
377
+ identifier_source_line_details
378
+ end
379
+ end
380
+
381
+ class ImplementationProblem < Problem
382
+ def initialize(member, implementing_type)
383
+ @member = member
384
+ @implementing_type = implementing_type
385
+ end
386
+
387
+ def message
388
+ if file.type == @implementing_type
389
+ "#{core_message}:"
390
+ else
391
+ "#{core_message} in #@implementing_type:"
392
+ end
393
+ end
394
+
395
+ def detail ; @member end
396
+
397
+ def details
398
+ "\e[0m * \e[1m#{@member.name}\e[0m (#{@member.type})\e[0m"
399
+ end
400
+ end
401
+
402
+ class MissingImplementation < ImplementationProblem
403
+ def core_message
404
+ "Missing implementation"
405
+ end
406
+ end
407
+
408
+ class WrongImplementation < ImplementationProblem
409
+ def core_message
410
+ "Wrong implementation"
411
+ end
412
+ end
413
+
414
+ class MissingPublicDefinition < Problem
415
+ def message ; "Missing public class or function definition." end
416
+ end
417
+
418
+ class Unknown < Problem
419
+ def initialize(message) @message = message end
420
+
421
+ def message
422
+ @message.sub(/^Error:\s+/, "")
423
+ end
424
+
425
+ def details
426
+ if source_line
427
+ source_line_details
428
+ end
429
+ end
430
+ end
431
+
432
+ def print_report
433
+ unless message == last_message
434
+ print message_column
435
+ print dummy_line_number_column unless message_column_overflowed?
436
+ puts
437
+ end
438
+
439
+ if details and details != last_details
440
+ print details_column
441
+ print line_number_column
442
+ puts
443
+ end
444
+
445
+ if extra_details and extra_details != last_extra_details
446
+ for line in extra_details
447
+ puts "\e[34m #{line.chomp}\e[0m"
448
+ end
449
+ end
450
+ end
451
+
452
+ MESSAGE_COLUMN_WIDTH = 56
453
+ LINE_NUMBER_COLUMN_WIDTH = 4
454
+
455
+ def message_column_overflowed?
456
+ message_column.size > MESSAGE_COLUMN_WIDTH
457
+ end
458
+
459
+ def message_column
460
+ ljust(" #{message}", MESSAGE_COLUMN_WIDTH)
461
+ end
462
+
463
+ def details_column
464
+ ljust(" #{details} ", MESSAGE_COLUMN_WIDTH)
465
+ end
466
+
467
+ def line_number_column
468
+ rjust("\e[1m#{line_number}\e[0m", LINE_NUMBER_COLUMN_WIDTH)
469
+ end
470
+
471
+ def dummy_line_number_column
472
+ rjust("\e[0m|\e[0m", LINE_NUMBER_COLUMN_WIDTH)
473
+ end
474
+
475
+ def last_message
476
+ previous.message if previous
477
+ end
478
+
479
+ def last_details
480
+ previous.details if previous
481
+ end
482
+
483
+ def last_extra_details
484
+ previous.extra_details if previous
485
+ end
486
+
487
+ def previous
488
+ file.problem_before(self)
489
+ end
490
+
491
+ def source_line_details
492
+ "\e[0m ... #{source_line}\e[0m"
493
+ end
494
+
495
+ def identifier_source_line_details
496
+ "\e[0m ... #{problematic_identifier_pre}" +
497
+ "\e[1;4m#{problematic_identifier}\e[0m" +
498
+ "#{problematic_identifier_post}"
499
+ end
500
+
501
+ def problematic_identifier_pre
502
+ identifier_source_line_parts[0]
503
+ end
504
+
505
+ def problematic_identifier
506
+ identifier_source_line_parts[1]
507
+ end
508
+
509
+ def problematic_identifier_post
510
+ identifier_source_line_parts[2]
511
+ end
512
+
513
+ def identifier_source_line_parts
514
+ source_line =~ /^(.{#{column_number}})([\w$]+)(.*)$/
515
+ [$1, $2, $3]
516
+ end
517
+
518
+ def member_details
519
+ bullet_details(@member)
520
+ end
521
+
522
+ def bullet_details(content)
523
+ "\e[0m * #{content}\e[0m"
524
+ end
525
+ end
526
+ end