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/README.rdoc ADDED
@@ -0,0 +1,182 @@
1
+ = ASAutotest
2
+
3
+ ASAutotest is an automatic compilation and testing tool ideally suited
4
+ for non-IDE ActionScript development.
5
+
6
+ It operates by watching your entire source directory, triggering a
7
+ recompilation as soon as any source file changes. Compilation errors
8
+ are reported to the terminal in a nice way. To keep compilation times
9
+ to a minimum, it uses the Flex Compiler Shell (FCSH).
10
+
11
+ There is also support for automatically running tests after a
12
+ successful compilation, but this currently requires some manual glue,
13
+ is undocumented and a bit hard to set up (once set up, though, it
14
+ works really well). For a working example of the kind of glue you
15
+ need, see <tt><test-project/src/specification.as></tt>.
16
+
17
+
18
+ == Installation
19
+
20
+ The easiest way to install ASAutotest is via Rubygems:
21
+
22
+ $ sudo gem install asautotest
23
+
24
+ You can also install ASAutotest by simply symlinking the executables
25
+ into your PATH:
26
+
27
+ $ ln -s ~/asautotest/bin/asautotest ~/bin
28
+ $ ln -s ~/asautotest/bin/flash-policy-server ~/bin
29
+
30
+ To get started, you need fcsh, which is part of the Flex SDK.
31
+ It should either be in your PATH, or you can set the <tt>FCSH</tt>
32
+ environment variable to the location of the <tt>fcsh</tt> executable.
33
+ (Unfortunately, symlinking <tt>fcsh</tt> into your PATH may not work;
34
+ if so, you will likely end up preferring to set the
35
+ environment variable.)
36
+
37
+ You can find the Flex SDK here:
38
+ http://opensource.adobe.com/wiki/display/flexsdk
39
+
40
+
41
+ == Usage
42
+
43
+ Once you have both <tt>asautotest</tt> and <tt>fcsh</tt> in place, try
44
+ this:
45
+
46
+ $ asautotest ~/my-project/src/main.as
47
+
48
+ This will compile <tt>main.as</tt>, using <tt>~/my-project/src</tt> as
49
+ the source directory, and then go to sleep, waiting for changes.
50
+ As soon as some <tt>*.as</tt> or <tt>*.mxml</tt> file in
51
+ <tt>~/my-project/src</tt> (or any subdirectory) changes, ASAutotest
52
+ will wake up and perform a recompilation.
53
+
54
+ By default, the resulting SWF is placed in a temporary directory and
55
+ then thrown away. If you want to use the SWF, you need to specify
56
+ where it should be placed, using the <tt>--output</tt> option (or
57
+ <tt>-o</tt> for short):
58
+
59
+ $ cd ~/my-project
60
+ $ asautotest src/main.as -o bin/my-project.swf
61
+
62
+ If you have additional source directores, add them using the
63
+ <tt>--source</tt> option (or <tt>-I</tt> for short):
64
+
65
+ $ asautotest ~/my-project/src/main.as -I ~/my-other-project/src
66
+
67
+ To link with SWC files, use the <tt>--library</tt> option (or
68
+ <tt>-l</tt> for short):
69
+
70
+ $ asautotest main.as -l ../lib/some-library.swc
71
+
72
+
73
+ == Autotesting
74
+
75
+ You can tell ASAutotest to run your SWF as a test using the
76
+ <tt>--test</tt> option (or <tt>-t</tt> for short):
77
+
78
+ $ asautotest spec.as --test
79
+
80
+ For this to work, your test must follow a rather complicated protocol
81
+ involving connecting to a socket and sending the test results in a
82
+ specific (non-standard) format. ASAutotest comes with adapter code
83
+ for ASSpec, so if your tests are written using ASSpec, then getting up
84
+ and running with autotesting should be easy.
85
+
86
+ Your <tt>foo_spec.as</tt> should look like this:
87
+
88
+ package
89
+ {
90
+ import asautotest.ASSpecRunner
91
+
92
+ public class foo_spec extends ASSpecRunner
93
+ {
94
+ public function foo_spec()
95
+ { super(new FooSuite) }
96
+ }
97
+ }
98
+
99
+ Then you need to add <tt>asautotest/assspec/src</tt> as a source
100
+ directory:
101
+
102
+ $ asautotest foo_spec.as --test -l asspec.swc -I ~/asautotest/asspec/src
103
+
104
+ Because the test communicates with ASAutotest through a socket, you
105
+ also need to run a Flash cross-domain policy server on port 843 (which
106
+ unfortunately requires root privileges); otherwise, the test will not
107
+ be able to connect to ASAutotest:
108
+
109
+ $ sudo flash-policy-server
110
+
111
+ This will just keep running, so you may want to start it in a screen:
112
+
113
+ $ screen sudo flash-policy-server
114
+
115
+ You should be all set for autotesting now. Try it out by changing
116
+ something in your code and watching the tests run automatically.
117
+ Note that the tests are only executed if all compilations succeed.
118
+
119
+
120
+ === Advanced autotesting
121
+
122
+ By default, ASAutotest expects the test to connect to port 50102.
123
+ If for some reason you need to use a different port (maybe you have
124
+ multiple ASAutotest instances running at the same time), you can use
125
+ the <tt>--test-port</tt> option to specify it:
126
+
127
+ $ asautotest foo_spec.as --test --test-port 54321
128
+
129
+ To change the port that the ASSpec adapter connects to, do this:
130
+
131
+ public function foo_spec()
132
+ { super(new FooSuite, 54321) }
133
+
134
+ If you need to write your own adapter to another testing framework,
135
+ you can use <tt>ASSpecRunner.as</tt> as a starting point.
136
+
137
+
138
+ == Multi-compilation
139
+
140
+ If you need to compile multiple SWFs (for example, one production SWF
141
+ and one test), use <tt>--</tt> (double hyphen) to divide the command
142
+ line into sections:
143
+
144
+ $ asautotest foo.as -o foo.swf -- foo_spec.as --test
145
+
146
+ This works with any number of source files:
147
+
148
+ $ asautotest foo.as -- bar.as -- baz.as -- quux.as
149
+
150
+ Options specified in such a section only apply to that source file.
151
+ In the following example, only <tt>bar.as</tt> is linked with
152
+ <tt>asspec.swc</tt> and compiled to <tt>bar.swf</tt>; the other two
153
+ files are just compiled to check for errors.
154
+
155
+ $ asautotest foo.as -- bar.as -o bar.swf -l asspec.swc -- baz.as
156
+
157
+ You can use <tt>---</tt> (triple hyphen) to specify global options:
158
+
159
+ $ asautotest foo.as -o foo.swf -- foo_spec.as --test --- -l asspec.swc
160
+
161
+ In the above example, both <tt>foo.as</tt> and <tt>foo_spec.as</tt>
162
+ would be linked with <tt>asspec.swc</tt>.
163
+
164
+
165
+ == License
166
+
167
+ Copyright (C) 2010, 2011 Go Interactive <http://gointeractive.se/>
168
+
169
+ This file is part of ASAutotest.
170
+
171
+ ASAutotest is free software: you can redistribute it and/or modify
172
+ it under the terms of the GNU General Public License as published by
173
+ the Free Software Foundation, either version 3 of the License, or
174
+ (at your option) any later version.
175
+
176
+ ASAutotest is distributed in the hope that it will be useful,
177
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
178
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
179
+ GNU General Public License for more details.
180
+
181
+ You should have received a copy of the GNU General Public License
182
+ along with ASAutotest. If not, see <http://www.gnu.org/licenses/>.
data/bin/asautotest ADDED
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+ # asautotest --- automatically compile and test ActionScript code
4
+ # Copyright (C) 2010, 2011 Go Interactive
5
+
6
+ # This file is part of ASAutotest.
7
+
8
+ # ASAutotest is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+
13
+ # ASAutotest is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with ASAutotest. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ require "rubygems"
22
+ require "pathname"
23
+ require "tmpdir"
24
+
25
+ module ASAutotest
26
+ current_directory = File.dirname(Pathname.new(__FILE__).realpath)
27
+ ROOT = File.expand_path(File.join(current_directory, ".."))
28
+ end
29
+
30
+ $: << File.join(ASAutotest::ROOT, "lib")
31
+
32
+ ENV["ASAUTOTEST_ROOT"] = ASAutotest::ROOT
33
+
34
+ require "asautotest/compilation-output-parser"
35
+ require "asautotest/compilation-result"
36
+ require "asautotest/compilation-runner"
37
+ require "asautotest/compiler-shell"
38
+ require "asautotest/logging"
39
+ require "asautotest/problematic-file"
40
+ require "asautotest/stopwatch"
41
+ require "asautotest/test-runner"
42
+ require "asautotest/utilities"
43
+
44
+ module ASAutotest
45
+ FCSH = ENV["FCSH"] || "fcsh"
46
+ FLASHPLAYER = ENV["FLASHPLAYER"] || "flashplayer"
47
+ WATCH_GLOB = "**/[^.]*.{as,mxml}"
48
+ DEFAULT_TEST_PORT = 50102
49
+ GROWL_ERROR_TOKEN = "ASAutotest-#{ARGV.inspect.hash}"
50
+
51
+ class << self
52
+ attr_accessor :growl_enabled
53
+ attr_accessor :displaying_growl_error
54
+ end
55
+
56
+ class CompilationRequest
57
+ attr_reader :source_file_name
58
+ attr_reader :source_directories
59
+ attr_reader :library_file_names
60
+ attr_reader :test_port
61
+ attr_reader :output_file_name
62
+ attr_reader :extra_mxmlc_options
63
+
64
+ def production? ; @production end
65
+ def test? ; @test end
66
+ def temporary_output? ; @temporary_output end
67
+
68
+ def initialize(options)
69
+ @source_file_name = options[:source_file_name]
70
+ @source_directories = options[:source_directories]
71
+ @library_file_names = options[:library_file_names]
72
+ @production = options[:production?]
73
+ @test = options[:test?]
74
+ @test_port = options[:test_port] || DEFAULT_TEST_PORT
75
+ @extra_mxmlc_options = options[:extra_mxmlc_options] || []
76
+
77
+ if options.include? :output_file_name
78
+ @output_file_name = File.expand_path(options[:output_file_name])
79
+ else
80
+ @output_file_name = get_temporary_output_file_name
81
+ @temporary_output = true
82
+ end
83
+ end
84
+
85
+ def get_temporary_output_file_name
86
+ "#{Dir.tmpdir}/asautotest-#{get_random_token}.swf"
87
+ end
88
+
89
+ def get_random_token
90
+ "#{(Time.new.to_f * 1000).to_i}-#{(rand * 1_000_000).to_i}"
91
+ end
92
+
93
+ def compilation_command
94
+ if @compile_id
95
+ %{compile #@compile_id}
96
+ else
97
+ build_string do |result|
98
+ result << %{mxmlc}
99
+ for source_directory in @source_directories do
100
+ result << %{ -compiler.source-path=#{source_directory}}
101
+ end
102
+ for library in @library_file_names do
103
+ result << %{ -compiler.library-path=#{library}}
104
+ end
105
+ result << %{ -output=#@output_file_name}
106
+ result << %{ -static-link-runtime-shared-libraries}
107
+ result << %{ -compiler.strict}
108
+ result << %{ -debug} unless @production
109
+ for option in @extra_mxmlc_options
110
+ result << %{ #{option}}
111
+ end
112
+ result << %{ #@source_file_name}
113
+ end
114
+ end
115
+ end
116
+
117
+ attr_accessor :compile_id
118
+ attr_accessor :index
119
+ end
120
+
121
+ class Main
122
+ include Logging
123
+
124
+ def initialize(options)
125
+ initialize_growl if options[:enable_growl?]
126
+ @typing = options[:typing]
127
+ @compilation_requests = options[:compilation_requests].
128
+ map(&method(:make_compilation_request))
129
+ end
130
+
131
+ def initialize_growl
132
+ begin
133
+ require "growl"
134
+ if Growl.installed?
135
+ ASAutotest::growl_enabled = true
136
+ else
137
+ shout "You need to install the ‘growlnotify’ tool."
138
+ say "Alternatively, use --no-growl to disable Growl notifications."
139
+ exit -1
140
+ end
141
+ rescue LoadError
142
+ hint "Hint: Install the ‘growl’ gem to enable Growl notifications."
143
+ end
144
+ end
145
+
146
+ def make_compilation_request(options)
147
+ options[:source_file_name] =
148
+ File.expand_path(options[:source_file_name])
149
+ implicit_source_directory =
150
+ File.dirname(File.expand_path(options[:source_file_name])) + "/"
151
+ options[:source_directories] = options[:source_directories].
152
+ map { |directory_name| File.expand_path(directory_name) + "/" }
153
+ options[:source_directories] << implicit_source_directory unless
154
+ options[:source_directories].include? implicit_source_directory
155
+ options[:library_file_names] = options[:library_file_names].
156
+ map { |file_name| File.expand_path(file_name) }
157
+
158
+ CompilationRequest.new(options)
159
+ end
160
+
161
+ def self.run(*arguments)
162
+ new(*arguments).run
163
+ end
164
+
165
+ def run
166
+ print_header
167
+ start_compiler_shell
168
+ build
169
+ monitor_changes
170
+ end
171
+
172
+ def say_tabbed(left, right)
173
+ say("#{left} ".ljust(21) + right)
174
+ end
175
+
176
+ def format_file_name(file_name)
177
+ if file_name.start_with? ENV["HOME"]
178
+ file_name.sub(ENV["HOME"], "~")
179
+ else
180
+ file_name
181
+ end
182
+ end
183
+
184
+ def print_header
185
+ new_logging_section
186
+
187
+ for request in @compilation_requests
188
+ say "\e[1m#{File.basename(request.source_file_name)}\e[0m"
189
+
190
+ for source_directory in request.source_directories
191
+ say_tabbed " Source directory:",
192
+ format_file_name(source_directory)
193
+ end
194
+
195
+ for library in request.library_file_names
196
+ say_tabbed " Library:", format_file_name(library)
197
+ end
198
+
199
+ unless request.extra_mxmlc_options.empty?
200
+ say_tabbed " Extra options:", request.extra_mxmlc_options * " "
201
+ end
202
+
203
+ if request.temporary_output? and not request.test?
204
+ say " Not saving output SWF (use --output=FILE.swf to specify)."
205
+ say " Not running as test (use --test to enable)."
206
+ elsif not request.temporary_output?
207
+ say_tabbed " Output file:",
208
+ "=> #{format_file_name(request.output_file_name)}"
209
+ elsif request.test?
210
+ say " Running as test (using port #{request.test_port})."
211
+ end
212
+
213
+ say " Compiling in production mode." if request.production?
214
+ end
215
+
216
+ say "Running in verbose mode." if Logging.verbose?
217
+
218
+ if @typing == :dynamic
219
+ say "Not warning about missing type declarations."
220
+ end
221
+
222
+ new_logging_section
223
+ end
224
+
225
+ def start_compiler_shell
226
+ @compiler_shell = CompilerShell.new \
227
+ :compilation_requests => @compilation_requests,
228
+ :typing => @typing
229
+ @compiler_shell.start
230
+ end
231
+
232
+ def monitor_changes
233
+ user_wants_out = false
234
+
235
+ Signal.trap("INT") do
236
+ user_wants_out = true
237
+ throw :asautotest_interrupt
238
+ end
239
+
240
+ until user_wants_out
241
+ require "fssm"
242
+ monitor = FSSM::Monitor.new
243
+
244
+ for source_directory in source_directories
245
+ monitor.path(source_directory, WATCH_GLOB) do |watch|
246
+ watch.update { handle_change }
247
+ watch.create { handle_change ; throw :asautotest_interrupt }
248
+ watch.delete { handle_change ; throw :asautotest_interrupt }
249
+ end
250
+ end
251
+
252
+ catch :asautotest_interrupt do
253
+ begin
254
+ monitor.run
255
+ rescue
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ def source_directories
262
+ @compilation_requests.map(&:source_directories).flatten.uniq
263
+ end
264
+
265
+ def handle_change
266
+ new_logging_section
267
+ whisper "Change detected."
268
+ build
269
+ end
270
+
271
+ def build
272
+ compile
273
+
274
+ for summary in @compilation.result.summaries
275
+ if summary[:successful?]
276
+ if compilation_successful? and summary[:request].test?
277
+ run_test(summary[:request])
278
+ end
279
+
280
+ if summary[:request].temporary_output?
281
+ delete_output_file(summary[:request].output_file_name)
282
+ end
283
+ end
284
+ end
285
+
286
+ whisper "Ready."
287
+ end
288
+
289
+ def compilation_successful?
290
+ @compilation.result.successful?
291
+ end
292
+
293
+ def n_problems
294
+ @compilation.result.n_problems
295
+ end
296
+
297
+ def n_problematic_files
298
+ @compilation.result.n_problematic_files
299
+ end
300
+
301
+ def compile
302
+ @compilation = CompilationRunner.new \
303
+ @compiler_shell, :typing => @typing
304
+ @compilation.run
305
+ end
306
+
307
+ def run_test(request)
308
+ TestRunner.new(request.output_file_name, request.test_port).run
309
+ end
310
+
311
+ def delete_output_file(file_name)
312
+ begin
313
+ File.delete(file_name)
314
+ whisper "Deleted binary."
315
+ rescue Exception => exception
316
+ shout "Failed to delete binary: #{exception.message}"
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ def print_usage
323
+ warn "\
324
+ usage: asautotest FILE.as [--test|-o FILE.swf] [-I SRCDIR|-l FILE.swc]...
325
+ asautotest FILE.as [OPTION] [-- FILE.as [OPTION]]... [--- OPTIONS...]"
326
+ end
327
+
328
+ def new_compilation_request
329
+ { :source_directories => [], :library_file_names => [], :extra_mxmlc_options => [] }
330
+ end
331
+
332
+ $compilation_requests = [new_compilation_request]
333
+ $typing = nil
334
+ $verbose = false
335
+ $enable_growl = RUBY_PLATFORM =~ /darwin/
336
+ $parsing_global_options = false
337
+
338
+ until ARGV.empty?
339
+ request = $compilation_requests.last
340
+ requests = $parsing_global_options ? $compilation_requests : [request]
341
+ case argument = ARGV.shift
342
+ when /^--output(?:=(\S+))?$/, "-o"
343
+ if request.include? :output_file_name
344
+ warn "asautotest: only one ‘--output’ allowed per source file"
345
+ warn "asautotest: use ‘--’ to separate multiple source files"
346
+ print_usage ; exit -1
347
+ else
348
+ request[:output_file_name] = ($1 || ARGV.shift)
349
+ end
350
+ when "--production"
351
+ for request in requests
352
+ request[:production?] = true
353
+ end
354
+ when "--test"
355
+ if $parsing_global_options
356
+ warn "asautotest: option ‘--test’ cannot be used globally"
357
+ print_usage ; exit -1
358
+ else
359
+ request[:test?] = true
360
+ end
361
+ when "--test-port(?:=(\S+))?"
362
+ value = ($1 || ARGV.shift).to_i
363
+ for request in requests
364
+ request[:test_port] = value
365
+ end
366
+ when /^--library(?:=(\S+))?$/, "-l"
367
+ value = ($1 || ARGV.shift)
368
+ for request in requests
369
+ request[:library_file_names] << value
370
+ end
371
+ when /^--source=(?:=(\S+))?$/, "-I"
372
+ value = ($1 || ARGV.shift)
373
+ for request in requests
374
+ request[:source_directories] << value
375
+ end
376
+ when /^--asspec-adapter-source$/
377
+ value = File.join(ASAutotest::ROOT, "asspec", "src")
378
+ for request in requests
379
+ request[:source_directories] << value
380
+ end
381
+ when "--dynamic-typing"
382
+ $typing = :dynamic
383
+ when "--static-typing"
384
+ $typing = :static
385
+ when "--verbose"
386
+ $verbose = true
387
+ when "--no-growl"
388
+ $enable_growl = false
389
+ when /^--mxmlc-option(?:=(.*))?$/, "-X"
390
+ value = ($1 || ARGV.shift)
391
+ for request in requests
392
+ request[:extra_mxmlc_options] << value
393
+ end
394
+ when "--"
395
+ if request.include? :source_file_name
396
+ $compilation_requests << new_compilation_request
397
+ else
398
+ warn "asautotest: no source file found before ‘--’"
399
+ print_usage ; exit -1
400
+ end
401
+ when "---"
402
+ if $parsing_global_options
403
+ warn "asautotest: only one ‘---’ section allowed"
404
+ print_usage ; exit -1
405
+ else
406
+ $parsing_global_options = true
407
+ end
408
+ when /^-/
409
+ warn "asautotest: unrecognized argument: #{argument}"
410
+ print_usage ; exit -1
411
+ else
412
+ if request.include? :source_file_name
413
+ warn "asautotest: use ‘--’ to separate multiple source files"
414
+ print_usage ; exit -1
415
+ else
416
+ request[:source_file_name] = argument
417
+ end
418
+ end
419
+ end
420
+
421
+ unless $compilation_requests.first[:source_file_name]
422
+ warn "asautotest: please specify a source file to be compiled"
423
+ print_usage ; exit -1
424
+ end
425
+
426
+ ASAutotest::Logging.verbose = $verbose
427
+
428
+ begin
429
+ ASAutotest::Main.run \
430
+ :compilation_requests => $compilation_requests,
431
+ :typing => $typing,
432
+ :enable_growl? => $enable_growl
433
+ rescue Interrupt
434
+ end
435
+
436
+ # Signal successful exit.
437
+ exit 200