asautotest 0.0.1

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